pax_global_header00006660000000000000000000000064141766420530014522gustar00rootroot0000000000000052 comment=701aeda05e39706681c05dc282ab6d90016c3997 spyne-spyne-2.14.0/000077500000000000000000000000001417664205300141005ustar00rootroot00000000000000spyne-spyne-2.14.0/.coveragerc000066400000000000000000000000321417664205300162140ustar00rootroot00000000000000[run] omit = spyne/test/* spyne-spyne-2.14.0/.gitignore000066400000000000000000000015651417664205300160770ustar00rootroot00000000000000.data .cache .eggs # python cruft *.pyc *.pyo *$py.class *.egg *.egg-info __pycache__ dist build pip-log.txt MANIFEST # test cruft .tox .noseids .coverage .coverage.* coverage.xml htmlcov test_html test_file_storage virt-* .pytest_cache ## test output spyne/test/*.html spyne/test/store test_result* Test*.html ## run_tests.sh jython-installer-* Python-2.*.* Python-3.*.* ## wsi-test wsi-test-tools WSI_Test_Java_Final_1.1.zip spyne/test/interop/config.xml spyne/test/config.xml # sphinx cruft .buildinfo doc/docs # latex cruft get_utc_time.pdf pres.pdf *.aux *.log *.nav *.out *.snm *.toc *.vrb *.backup # 2to3 cruft *.bak # output from examples examples/file_manager/files/ examples/xml/*.xsd # nuitka cruft *.build # cruft produced by non-spyne code need to go to your global ignores file. see # https://help.github.com/articles/ignoring-files/#create-a-global-gitignore spyne-spyne-2.14.0/.landscape.yaml000066400000000000000000000001721417664205300167740ustar00rootroot00000000000000ignore-paths: - bin - examples ignore-patterns: - __init__.py requirements: - requirements/test_requirements.txt spyne-spyne-2.14.0/.travis.yml000066400000000000000000000006031417664205300162100ustar00rootroot00000000000000language: python matrix: include: - python: "2.7" env: TEST_SUITE=test - python: "3.4" env: TEST_SUITE=test_python3 - python: "3.5" env: TEST_SUITE=test_python3 - python: "3.6" env: TEST_SUITE=test_python3 - python: "3.7" env: TEST_SUITE=test_python3 install: - pip install -r requirements/test_requirements.txt script: - python setup.py $TEST_SUITE spyne-spyne-2.14.0/CHANGELOG.rst000066400000000000000000000310701417664205300161220ustar00rootroot00000000000000 Changelog ========= spyne-2.14.0 ------------ * Python 3.10 support. * msgpack 1.x support. * sqlalchemy 1.2, 1.3, 1.4 support. * Ported the Http subsystem to Python 3. * Support for interface document customization. Thanks to github.com/PyGuDev * Dropped deprecated integration for SqlAlchemy. In other words, ``spyne.model.table`` is gone. * Bug fixes and one DoS fix: GHSL-2021-115. Thanks to github.com/kevinbackhouse and Github security team. * Jenkins run: https://jenkins.arskom.com.tr/job/spyne/job/origin-stable/192/ spyne-2.13.16 ------------- * Python 3.9 support * A lot of small fixes here and there. spyne-2.13.15 ------------- * A lot of small fixes here and there blocking the stable release. spyne-2.13.14-beta ------------------- * Test and fix polymorphic roundtrip via XmlDocument. * Implement support for relationship tables with >2 foreign keys. spyne-2.13.13-beta ------------------- * Implement wrapped/not_wrapped for ComplexModel and HierDict family. spyne-2.13.12-beta ------------------- * Python 3 support is now beta !!!!. All tests pass and there are no known bugs spyne-2.13.11-alpha ------------------- * SOAP: Repaired MtoM parsing. * Faults: The Fault api is now much more consistent. * Various fixes across the board. * ``sqla_column_args`` is now only a dict. It can override column name without changing mapped object attribute name. * Various Python 3 fixes for the Twisted frontend. spyne-2.13.4-alpha ------------------ * ``Date(format="%Y")`` no longer works. Use ``Date(date_format="%Y")`` just like the api docs say spyne-2.13.3-alpha ------------------ * Add support for sqlalchemy-1.2. * Implement _logged for @rpc. * Fix memory leak in ComplexModelBase.as_dict. * Switch to homegrown jenkins as test infrastructure. See https://jenkins.arskom.com.tr * Fix decimal totalDigits blunder. spyne-2.13.2-alpha ------------------ * ``ServiceBase`` is deprecated in favor of ``Service``. It's just a name change in order to make it consistent with the rest of the package. ServiceBase will be kept until Spyne 3. * Introduced internal keys for services and methods. Uniqueness is enforced during Application instantiation. If your server refuses to boot after migrating to 2.13 raising ``MethodAlreadyExistsError``, explicitly setting a unique ``__service_name__`` in one of the offending ``ServiceBase`` subclasses should fix the problem. See 2fee1435c30dc50f7503f0915b5e56220dff34d0 for the change. * EXPERIMENTAL library-wide Python 3 Support! Yay! * MessagePack uses backwards-compatible raws with a hard-coded UTF-8 encoding for Unicode (non-ByteArray) types. Please open an issue if not happy with this. * It's the transports' job to decide on a codec. Use UTF-8 when in doubt, as that's what we're doing. * Avoid the async keyword for Python 3.7. * Float rounding behaviour seems to have changed in Python 3. In Python 2, ``round(2.5) = 3`` and ``round(3.5) = 4`` whereas in Python 3, ``round(2.5) = 2`` and ``round(3.5) = 4``. This is called half-to-even rounding and while being counterintuitive, it seems to make better sense from a statistical standpoint. You will have to live with this or use ``decimal.Decimal``. This changes the way datetime and time microseconds are rounded. See ``test_datetime_usec`` and ``test_time_usec`` in ``spyne.test.model.test_primitive``. * ``spyne.model.Unicode`` used to tolerate (i.e. implicitly but not-so-silenty casted to ``str``) int values. This is no longer the case. If you want to set proper numbers to a Unicode-designated field, you must provide a casting function. Generally, ``Unicode(cast=str)`` is what you want to do. See d495aa3d56451bd02c0076a9a1f14c6450eadc8e for the change. * ``exc_table`` is deprecated in favour of ``exc_db``\. Please do a s/exc_table/exc_db/g in your codebase when convenient. * Django 1.6 support dropped. Supporting 1.7-1.10. * Bare methods with non-empty output now have ``descriptior.body_style = spyne.BODY_STYLE_EMPTY_OUT_BARE``\, which was ``spyne.BODY_STYLE_EMPTY`` before. This hould not break anything unless you are doing some REAL fancy stuff in the method decorators or service events. * Auxproc is DEPRECATED. Just get rid of it. * ``spyne.protocol.dictdoc.simple``, ``spyne.server.twisted.http`` and ``spyne.server.django`` are not experimental anymore. * No major changes otherwise but we paid a lot of technical debt. e.g. We revamped the test infrastructure. * ``_in_variable_names`` argument to ``@rpc`` was deprecated in favour of ``_in_arg_names`` * ``_udp`` argument to ``@rpc`` was deprecated in favour of ``_udd``. UDP is too well known as user datagram protocol which could be confusing. * ``_when`` argument to ``@mrpc`` now needs to be a callable that satisfies the ``f(self, ctx)`` signature. It was ``f(self)`` before. * Attachment is removed. It's been deprecated since ages. * Usual bug fixes. spyne-2.12.15 ------------- * Fix graceful import failures for Python 3 spyne-2.12.14 ------------- * Fixed inclusive ranges for DateTime and friends. #506 * Turns out SQLAlchemy 1.1 causes funky crashes. We're fixated on 1.0 until the issue can be investiaged. * Implemented MIN_GC_INTERVAL to prevent excessive calls to ``gc.collect()`` See issue #472. PR: #515 spyne-2.12.13 ------------- * Dang. spyne-2.12.12 ------------- * Return to pre 2.12 behaviour - coroutine exceptions are not silenced but sent up the stack. This is backport of 2.13 fix. * Proper serialization of ComplexModels subclasses of other ComplexModels when initialized from lists. * Minor bug fixes all around. spyne-2.12.11 ------------- * Fix self-referential relationships pointing the wrong way * Fix wrong use of string interpolation operator in logging call slowing Spyne down for no visible reason * Detect and prevent name clashes between the foreign key column name and the object itself. * Silence a lot of (wrong) customized class instantiation warnings. spyne-2.12.10 ------------- * IpAddress types now support PostgreSQL's PGInet. * Drop trial for twisted tests and switch to pytest-twisted. * ``_safe_set`` now returns True on success so that protocols can react accordingly. * \*DictDoc now logs properly whether a value is discarded or passed to the deserialized instance. * Minor bug fixes here and there. spyne-2.12.9 ------------ * Make ``DateTime`` handle unicode date format strings for Python 2. * Fix idle timer not starting on connectionMade for ``MessagePackTransportBase`` spyne-2.12.7 ------------ * Not beta anymore. Woo! * Made ServiceBase subclasses reusable * Implemented class customization via ``__getitem__``\. * Fixed an ``ImportError`` running Python 3.4 under Pydev using PyCharm. (Eclipse still has issues, see `issue #432 `_. Any help would be much appreciated) * Fixed DateTime corner case with μs values between 999995 and 999999. * Help misguided user code that returns an int for a string type by implicitly yet not-so-silently converting the ``int``/``long`` to ``str``\. * Fixed \*Cloth sometimes getting stuck ``repr()``\'ing passed instance. * Fixed ``SimpleDictDocument`` confusing a missing value and an empty value for array types. When the client wants to denote an empty array, it should pass ``array_field=empty``\. Normally it passes something along the lines of: ``array_field[0]=Something&array_field[1]=SomethingElse``\. * Split ``MessagePackServerBase`` to ``MessagePackTransportBase`` and ``MessagePackServerBase``\. No API was harmed during this change. * Implement optional idle timeout for ``MessagePackTransportBase``\. * Add support for PGObjectJson, PGObjectXml and PGFileJson to sql table reflection. * ``log_repr`` now consults ``NATIVE_MAP`` as a last resort before freaking out. * Removed some dead code. spyne-2.12.6-beta ----------------- * Thanks to `issue #446 `_ we noticed that in some cases, SOAP messages inside HTTP requests got processed even when the request method != 'POST'. This got resolved, but you should check whether this is the case in your setup and take the necessary precautions before deploying Spyne. spyne-2.12.[12345]-beta ----------------------- * Many bugs fixed very quick. spyne-2.12.0-beta ----------------- * XmlObject: Support for ``attribute_of`` is removed. * NullServer now supports async. * XmlCloth was rewritten while less sleep-deprived. * ProtocolBase now also implements serializing primitives to unicode. * Add initial support for input polymorphism to XmlDocument (parsing xsi:type). It's an experimental feature. * Add output polymorphism for all protocols. It's off-by-default for XmlDocument and friends, on-by-default for others. * Add stub implementation for SOAP 1.2 * Add initial implementation for SOAP 1.2 Faults. * Remove the deprecated ``interface`` argument to ``Application``\. * HierDictDocument's broken wrapped dict support was fixed. Even though this is supposed to break compatibility with 2.11, virtually no one seems to use this feature. Only now it's mature enough to be set on stone. Let us know! * We now validate kwargs passed to ``@rpc``\. Be sure to test your daemons before deploying for production, because if you got leftovers, the server will refuse to boot! * It's now forbidden (by assert) to inherit from a customized class. * It's also forbidden (by convention) to instantiate a customized class. Don't do it! The warning will be converted to an assert in the future. spyne-2.11.0 ------------ * Experimental Python 3 Support for all of the Xml-related (non-Html) components. * Add support for altering output protocol by setting ``ctx.out_protocol``. * Add returning ctx.out_string support to null server (The ``ostr`` argument). * Add support for XmlData modifier. It lets mapping the data in the xml body to an object field via xsd:simpleContent. * Remove deprecated ``JsonObject`` identifier. Just do a gentle ``s/JsonObject/JsonDocument/g`` if you're still using it. * SQLAlchemy: Implement storing arrays of simple types in a table. * SQLAlchemy: Make it work with multiple foreign keys from one table to another. * SQLAlchemy: Implement a hybrid file container that puts file metadata in a json column in database and and file data in file system. Fully supported by all protocols as a binary File.Value instance. * Implement an Xml Schema parser. * Import all model markers as well as the ``@rpc``\, ``@srpc``\, ``@mrpc``, ``ServiceBase`` and ``Application`` to the root ``spyne`` package. * Implement JsonP protocol. * Implement SpyneJsonRpc 1.0 protocol -- it supports request headers. Sample request: ``{"ver":1, "body": {"div": [4,2]}, "head": {"id": 1234}}`` Sample response: ``{"ver":1, "body": 2}`` Sample request: ``{"ver":1, "body": {"div": {"dividend":4,"divisor":0]}}`` Sample response: ``{"ver":1, "fault": {"faultcode": "Server", "faultstring": "Internal Error"}}}`` * Steal and integrate the experimental WebSocket tranport from Twisted. * Support Django natively using `spyne.server.django.DjangoView` and `spyne.server.django.DjangoServer`. * It's now possible to override the ``JsonEncoder`` class ``JsonDocument`` uses. * Remove hard-coded utf-8 defaults from almost everywhere. * Remove hard-coded pytz.utc defaults from everywhere. Use spyne.LOCAL_TZ to configure the default time zone. * As a result of the above change, ``datetime`` objects deserialized by Spyne are forced to the above time zone during soft validation (nothing should have changed from the user code perspective). * Add ``default_factory`` to ModelBase customizer. It's a callable that produces default values on demand. Suitable to be used with e.g. lambdas that return mutable defaults. * New ``spyne.util.AttrDict`` can be used for passing various dynamic configuration data. * ``child_attrs`` can now be passed to the ComplexModelBase customizer in order to make object-specific in-place customizations to child types. * Add mapper between Django models and `spyne.util.django.DjangoComplexModel` types. * Spyne now tracks subclasses and adds them to the interface if they are in the same namespace as their parent. * Simple dictionary protocol's ``hier_delim`` default value is now '.' * Fixes support for XmlAttributes with max_occurs>1 referencing the same 'attribute_of' element in a ComplexModel subclass. * Renders ``spyne.model.File`` as ``twisted.web.static.File`` when using HttpRpc over ``TwistedWebResource``. This lets twisted handle Http 1.1-specific functionality like range requests. * Many, many, many bugs fixed. Check the documentation at http://spyne.io/docs for changelogs of the older versions spyne-spyne-2.14.0/CONTRIBUTING.rst000066400000000000000000000136101417664205300165420ustar00rootroot00000000000000First things first: No questions in the issue tracker please. Spyne maintainers answer questions in the Spyne mailing list ( https://lists.spyne.io/lists/people ) or StackOverflow questions tagged ``[spyne]`` (https://stackoverflow.com/questions/tagged/spyne). Questions filed in the issue tracker get closed without being looked at. If you are not here to ask a question, please read on. -------------------------------- Hey there! Thanks a lot for considering to contribute to Spyne! Here are some things you might want to know. Do you actually need your code in Spyne? ======================================== Spyne code is highly modular. If you are adding a new transport, protocol, or model type, you might want to keep the code in a separate package. This will make it possible to work at your own pace, use your own code style, your own licensing terms, etc. However, if: - You are fixing a bug in Spyne - Your code is tightly coupled with Spyne (i.e. it's using private APIs, needs monkey-patching to work outside of Spyne, etc.) - You are adding a new feature to a partly-finished part of Spyne - You are not sure ... just send a pull request and we will talk. Spyne development guidelines ============================ If you wish to contribute to Spyne's development, start by creating a personal fork on GitHub. When you are ready to push to the upstream repository, submit a pull request to bring your work to the attention of the core committers. They will respond to review your patch and act accordingly. Code format ----------- The only hard rule we have is to avoid tests in code (you are supposed to put all tests in the ``spyne.tests`` package). A test is a member function of a `TestSomething` class inside a module from ``spyne.tests`` package. This means eg. no doctests, no `if __name__ == '__main__'` etc. Spyne's test infrastructure is complex enough as it is and we won't complicate it any further for your hippie testing tool-or-methodology-du-jour for no good reason. That said, there are also a bunch of other guidelines that are mostly followed by Spyne code. They are not really mandatory for your pull request to go in but a consistent code style *does* make the lives of the future generations much easier. They are, in no particular order: - Use stair-style indentation for long lines. E.g. :: this_is_the_result_of = a_function_with_a_very_long_name( that_takes, a_lot_of, arguments.that_have, long_yet_informative_names, which_makes_the_code, a_pleasure_to_read, yet_sometimes_a_pain_to_write) where the rightmost character of the last line is ALWAYS on the 80th column. - Every module starts with: 1. Hashbang line (only if executable) 2. ``# encoding: utf8`` line or empty line. 3. Spyne license header. Leading and trailing lines with just ``#`` in them look nice. 4. Future imports (if needed) 5. Logger set up, i.e.: :: import logging logger = logging.getLogger(__name__) This is needed to catch any import-level logging. 6. Preferred order for import statements is as follows: - stdlib absolute imports - other absolute imports (grouped by package) - stdlib relative imports - other relative imports (grouped by package) - When in doubt, follow `PEP 8 `_ Have fun! Branching --------- For fixing an issue or adding a feature create a new branch from ``master`` branch. Github's pull-request feature is awesome, but there's a subtlety that's not totally obvious for newcomers: If you continue working on the branch that you used to submit a pull request, your commits will "pollute" the pull request until it gets merged. This is not a bug, but a feature -- it gives you the ability to address reviewers' concerns without creating pull requests over and over again. So, if you intend to work on other parts of spyne after submitting a pull request, please do move your work to its own branch and never submit a pull request from your master branch. This will give you the freedom to continue working on Spyne while waiting for your pull request to be reviewed. Pull requests ------------- When submitting a pull request, make sure: * Your commit messages are to-the-point and the actual patches are in line with the commit messages. Don't hesitate to push as many commits as possible. Git makes it very easy to split changes to relevant chunks once your work is done. You can either use ```git gui``` or ```git add -i``` -- they're a joy to work with once you get the hang of all this DVCS story. * You have added tests for the feature you're adding or the bug you're fixing. * You also have added short description of the changes in ``CHANGELOG.rst`` if you think it's needed. Do note that we keep the master branch on the upstream repository "clean" -- i.e. we never merge pull requests with failing tests. So make sure all old and new tests pass before submitting pull requests. Regular contributors may be invited to join as a core Spyne committer on GitHub. Even if this gives the core committers the power to commit directly to the core repository, we highly value code reviews and expect every significant change to be committed via pull requests. Miscellanous Remarks -------------------- * When overriding a method in a class use the ``super()`` to call the parent implementation instead of explicitly specifying the parent class. Not only this is mandatory for Python 3 compatibility, but also it's needed to correctly call all implementations of the overridden method according to MRO in cases of multiple inheritance. * Unit tests should test the root functionality as closely as possible. This way, instead of getting a less useful "something broke" alert, we'd get e.g. "something broke in the validation of customized types used as xml attributes" alert. spyne-spyne-2.14.0/LICENSE000066400000000000000000000757211417664205300151210ustar00rootroot00000000000000 Spyne uses code from multiple projects, as well as original code. This file contains the license information for all of these sources. ================================================================================ ================================================================================ Spyne, located under the spyne/ directory in the source distribution is offered under the LGPL license. -------------------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ================================================================================ ================================================================================ Spyne examples located under the examples/ directory in the source distribution are covered under the BSD License. -------------------------------------------------------------------------------- Copyright © Burak Arslan , Arskom Ltd. http://arskom.com.tr All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the owner nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ ================================================================================ Spyne uses some code from the Twisted project at http://twistedmatrix.com. -------------------------------------------------------------------------------- Copyright (c) 2001-2012 Allen Short Andy Gayton Andrew Bennetts Antoine Pitrou Apple Computer, Inc. Benjamin Bruheim Bob Ippolito Canonical Limited Christopher Armstrong David Reid Donovan Preston Eric Mangold Eyal Lotem Itamar Turner-Trauring James Knight Jason A. Mobarak Jean-Paul Calderone Jessica McKellar Jonathan Jacobs Jonathan Lange Jonathan D. Simms Jürgen Hermann Kevin Horn Kevin Turner Mary Gardiner Matthew Lefkowitz Massachusetts Institute of Technology Moshe Zadka Paul Swartz Pavel Pergamenshchik Ralph Meijer Sean Riley Software Freedom Conservancy Travis B. Hartwell Thijs Triemstra Thomas Herve Timothy Allen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ ================================================================================ Spyne distributes the six module within its source distribution: https://bitbucket.org/gutworth/six/ -------------------------------------------------------------------------------- Copyright (c) 2010-2013 Benjamin Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ ================================================================================ Spyne distributes the django-ipware package within its source distribution: https://github.com/un33k/django-ipware/tree/57897c03026913892e61a164bc8b022778802ab9 -------------------------------------------------------------------------------- The MIT License Copyright (c) Val Neekman @ Neekware Inc. http://neekware.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spyne-spyne-2.14.0/PEOPLE000066400000000000000000000005121417664205300150050ustar00rootroot00000000000000 Contributors (chronological order): Aaron Bickell Jamie Kirkpatrick Burak Arslan Tres Seaver Chris Austin Brad Allen github.com/Konjkov (see issue 211) Also see here for a complete list: https://github.com/arskom/spyne/contributors Ohloh also keeps track of such stats: http://ohloh.net/p/spyne spyne-spyne-2.14.0/README.rst000066400000000000000000000140661417664205300155760ustar00rootroot00000000000000**WARNING:** This is from spyne's development branch. This version is not released yet! Latest stable release can be found in the ``2_13`` branch. If you like and use Spyne, star it on `Github `_! About ===== Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. In other words, Spyne is a framework for building distributed solutions that strictly follow the MVC pattern, where Model = `spyne.model`, View = `spyne.protocol` and Controller = `user code`. Spyne comes with the implementations of popular transport, protocol and interface document standards along with a well-defined API that lets you build on existing functionality. The following are the primary sources of information about spyne: * Spyne's home page is: http://spyne.io/ * The latest documentation for all releases of Spyne can be found at: http://spyne.io/docs * The official source code repository is at: https://github.com/arskom/spyne * The official spyne discussion forum is at: people at spyne dot io. Subscribe either via http://lists.spyne.io/listinfo/people or by sending an empty message to: people-subscribe at spyne dot io. * You can download Spyne releases from `Github `_ or `PyPi `_. * Continuous Integration: https://jenkins.arskom.com.tr/job/spyne/ Requirements ============ Spyne source distribution is a collection of highly decoupled components, which makes it a bit difficult to put a simple list of requirements, as literally everything except ``pytz`` is optional. Python version -------------- Spyne 2.13 supports Python 2.7, 3.6, 3.7, 3.8, 3.9 and 3.10. Libraries --------- Additionally the following software packages are needed for various subsystems of Spyne: * A Wsgi server of your choice is needed to wrap ``spyne.server.wsgi.WsgiApplication`` * `lxml>=3.2.5 `_ is needed for any xml-related protocol. * `lxml>=3.4.1 `_ is needed for any html-related protocol. * `SQLAlchemy `_ is needed for ``spyne.model.complex.TTableModel``. * `pyzmq `_ is needed for ``spyne.client.zeromq.ZeroMQClient`` and ``spyne.server.zeromq.ZeroMQServer``. * `Werkzeug `_ is needed for using ``spyne.protocol.http.HttpRpc`` under a wsgi transport. * `PyParsing `_ is needed for using ``HttpPattern``'s with ``spyne.protocol.http.HttpRpc``\. * `Twisted `_ is needed for anything in ``spyne.server.twisted`` and ``spyne.client.twisted``. * `Django `_ (tested with 1.8 and up) is needed for anything in ``spyne.server.django``. * `Pyramid `_ is needed for ``spyne.server.pyramid.PyramidApplication``. * `msgpack>=1.0.0 `_ is needed for ``spyne.protocol.msgpack``. * `PyYaml `_ is needed for ``spyne.protocol.yaml``. * `simplejson `_ is used when found for ``spyne.protocol.json``. You are advised to add these as requirements to your own projects, as these are only optional dependencies of Spyne, thus not handled in its setup script. Installing ========== You first need to have package manager (pip, easy_install) installed. Spyne ships with a setuptools bootstrapper, so if setup.py refuses to run because it can't find setuptools, do: bin/distribute_setup.py You can add append --user to get it installed with $HOME/.local as prefix. You can get spyne via pypi: :: easy_install [--user] spyne or you can clone the latest master tree from Github: :: git clone git://github.com/arskom/spyne.git To install from source distribution, you can run the setup script as usual: :: python setup.py install [--user] If you want to make any changes to the Spyne code, just use :: python setup.py develop [--user] so that you can painlessly test your patches. Finally, to run the tests, you need to first install every single library that Spyne integrates with, along with additional packages like ``pytest`` or ``tox`` that are only needed when running Spyne testsuite. An up-to-date list is maintained in the ``requirements/`` directory, in separate files for both Python 2.7 and >=3.6. To install everything, run: :: pip install [--user] -r requirements/test_requirements.txt If you are still stuck on Python 2.x however, you should use: :: pip install [--user] -r requirements/test_requirements_py27.txt Assuming all dependencies are installed without any issues, the following command will run the whole test suite: :: python setup.py test Spyne's test harness has evolved a lot in the 10+ years the project has been active. It has 3 main stages: Traditional unit tests, tests that perform end-to-end testing by starting actual daemons that listen on real TCP sockets on hard-coded ports, and finally Django tests that are managed by tox. Naively running pytest etc in the root directory will fail as their auto-discovery mechanism was not implemented with Spyne's requirements in mind. Getting Support =============== Official support channels are as follows: - The official mailing list for both users and developers alike can be found at: http://lists.spyne.io/listinfo/people. - You can use the 'spyne' tag to ask questions on `Stack Overflow `_. - You can also use the `forum `_ on the project's github page. Please don't use the issue tracker for asking questions. It's a database that holds the most important information for the project, so we must avoid cluttering it as much as possible. Contributing ============ If you feel like helping out, see the CONTRIBUTING.rst file in the Spyne source distribution for starting points and general guidelines. spyne-spyne-2.14.0/SECURITY.md000066400000000000000000000006171417664205300156750ustar00rootroot00000000000000# Security Policy ## Supported Versions Use this section to tell people about which versions of your project are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | 2.13.x | :white_check_mark: | | <2.12.x | :x: | ## Reporting a Vulnerability Please send an email to the public email at the github profile address spyne-spyne-2.14.0/bin/000077500000000000000000000000001417664205300146505ustar00rootroot00000000000000spyne-spyne-2.14.0/bin/_preamble.py000066400000000000000000000013211417664205300171450ustar00rootroot00000000000000# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. # This makes sure that users don't have to set up their environment # specially in order to run these programs from bin/. # This helper is shared by many different actual scripts. It is not intended to # be packaged or installed, it is only a developer convenience. By the time # Spyne is actually installed somewhere, the environment should already be set # up properly without the help of this tool. import os import sys path = os.path.abspath(sys.argv[0]) while os.path.dirname(path) != path: if os.path.exists(os.path.join(path, 'spyne', '__init__.py')): sys.path.insert(0, path) break path = os.path.dirname(path) spyne-spyne-2.14.0/bin/build_schema.py000077500000000000000000000004141417664205300176430ustar00rootroot00000000000000#!/usr/bin/env python # This can be used to debug invalid Xml Schema documents. import sys from lxml import etree if len(sys.argv) != 2: print "Usage: %s " % sys.argv[0] sys.exit(1) f = open(sys.argv[1]) etree.XMLSchema(etree.parse(f)) spyne-spyne-2.14.0/bin/distribute_setup.py000077500000000000000000000424101417664205300206240ustar00rootroot00000000000000#!python """Bootstrap distribute installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from distribute_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import os import shutil import sys import time import fnmatch import tempfile import tarfile import optparse from distutils import log try: from site import USER_SITE except ImportError: USER_SITE = None try: import subprocess def _python_cmd(*args): args = (sys.executable,) + args return subprocess.call(args) == 0 except ImportError: # will be used for python 2.3 def _python_cmd(*args): args = (sys.executable,) + args # quoting arguments if windows if sys.platform == 'win32': def quote(arg): if ' ' in arg: return '"%s"' % arg return arg args = [quote(arg) for arg in args] return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 DEFAULT_VERSION = "0.6.49" DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" SETUPTOOLS_FAKED_VERSION = "0.6c11" SETUPTOOLS_PKG_INFO = """\ Metadata-Version: 1.0 Name: setuptools Version: %s Summary: xxxx Home-page: xxx Author: xxx Author-email: xxx License: xxx Description: xxx """ % SETUPTOOLS_FAKED_VERSION def _install(tarball, install_args=()): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) tar = tarfile.open(tarball) _extractall(tar) tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) # installing log.warn('Installing Distribute') if not _python_cmd('setup.py', 'install', *install_args): log.warn('Something went wrong during the installation.') log.warn('See the error message above.') # exitcode will be 2 return 2 finally: os.chdir(old_wd) shutil.rmtree(tmpdir) def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) tar = tarfile.open(tarball) _extractall(tar) tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) # building an egg log.warn('Building a Distribute egg in %s', to_dir) _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) finally: os.chdir(old_wd) shutil.rmtree(tmpdir) # returning the result log.warn(egg) if not os.path.exists(egg): raise IOError('Could not build the egg.') def _do_download(version, download_base, to_dir, download_delay): egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' % (version, sys.version_info[0], sys.version_info[1])) if not os.path.exists(egg): tarball = download_setuptools(version, download_base, to_dir, download_delay) _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) import setuptools setuptools.bootstrap_install_from = egg def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15, no_fake=True): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) was_imported = 'pkg_resources' in sys.modules or \ 'setuptools' in sys.modules try: try: import pkg_resources # Setuptools 0.7b and later is a suitable (and preferable) # substitute for any Distribute version. try: pkg_resources.require("setuptools>=0.7b") return except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): pass if not hasattr(pkg_resources, '_distribute'): if not no_fake: _fake_setuptools() raise ImportError except ImportError: return _do_download(version, download_base, to_dir, download_delay) try: pkg_resources.require("distribute>=" + version) return except pkg_resources.VersionConflict: e = sys.exc_info()[1] if was_imported: sys.stderr.write( "The required version of distribute (>=%s) is not available,\n" "and can't be installed while this script is running. Please\n" "install a more recent version first, using\n" "'easy_install -U distribute'." "\n\n(Currently using %r)\n" % (version, e.args[0])) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return _do_download(version, download_base, to_dir, download_delay) except pkg_resources.DistributionNotFound: return _do_download(version, download_base, to_dir, download_delay) finally: if not no_fake: _create_fake_setuptools_pkg_info(to_dir) def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15): """Download distribute from a specified location and return its filename `version` should be a valid distribute version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ # making sure we use the absolute path to_dir = os.path.abspath(to_dir) try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen tgz_name = "distribute-%s.tar.gz" % version url = download_base + tgz_name saveto = os.path.join(to_dir, tgz_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: log.warn("Downloading %s", url) src = urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = src.read() dst = open(saveto, "wb") dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def _no_sandbox(function): def __no_sandbox(*args, **kw): try: from setuptools.sandbox import DirectorySandbox if not hasattr(DirectorySandbox, '_old'): def violation(*args): pass DirectorySandbox._old = DirectorySandbox._violation DirectorySandbox._violation = violation patched = True else: patched = False except ImportError: patched = False try: return function(*args, **kw) finally: if patched: DirectorySandbox._violation = DirectorySandbox._old del DirectorySandbox._old return __no_sandbox def _patch_file(path, content): """Will backup the file then patch it""" f = open(path) existing_content = f.read() f.close() if existing_content == content: # already patched log.warn('Already patched.') return False log.warn('Patching...') _rename_path(path) f = open(path, 'w') try: f.write(content) finally: f.close() return True _patch_file = _no_sandbox(_patch_file) def _same_content(path, content): f = open(path) existing_content = f.read() f.close() return existing_content == content def _rename_path(path): new_name = path + '.OLD.%s' % time.time() log.warn('Renaming %s to %s', path, new_name) os.rename(path, new_name) return new_name def _remove_flat_installation(placeholder): if not os.path.isdir(placeholder): log.warn('Unkown installation at %s', placeholder) return False found = False for file in os.listdir(placeholder): if fnmatch.fnmatch(file, 'setuptools*.egg-info'): found = True break if not found: log.warn('Could not locate setuptools*.egg-info') return log.warn('Moving elements out of the way...') pkg_info = os.path.join(placeholder, file) if os.path.isdir(pkg_info): patched = _patch_egg_dir(pkg_info) else: patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) if not patched: log.warn('%s already patched.', pkg_info) return False # now let's move the files out of the way for element in ('setuptools', 'pkg_resources.py', 'site.py'): element = os.path.join(placeholder, element) if os.path.exists(element): _rename_path(element) else: log.warn('Could not find the %s element of the ' 'Setuptools distribution', element) return True _remove_flat_installation = _no_sandbox(_remove_flat_installation) def _after_install(dist): log.warn('After install bootstrap.') placeholder = dist.get_command_obj('install').install_purelib _create_fake_setuptools_pkg_info(placeholder) def _create_fake_setuptools_pkg_info(placeholder): if not placeholder or not os.path.exists(placeholder): log.warn('Could not find the install location') return pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) setuptools_file = 'setuptools-%s-py%s.egg-info' % \ (SETUPTOOLS_FAKED_VERSION, pyver) pkg_info = os.path.join(placeholder, setuptools_file) if os.path.exists(pkg_info): log.warn('%s already exists', pkg_info) return log.warn('Creating %s', pkg_info) try: f = open(pkg_info, 'w') except EnvironmentError: log.warn("Don't have permissions to write %s, skipping", pkg_info) return try: f.write(SETUPTOOLS_PKG_INFO) finally: f.close() pth_file = os.path.join(placeholder, 'setuptools.pth') log.warn('Creating %s', pth_file) f = open(pth_file, 'w') try: f.write(os.path.join(os.curdir, setuptools_file)) finally: f.close() _create_fake_setuptools_pkg_info = _no_sandbox( _create_fake_setuptools_pkg_info ) def _patch_egg_dir(path): # let's check if it's already patched pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') if os.path.exists(pkg_info): if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): log.warn('%s already patched.', pkg_info) return False _rename_path(path) os.mkdir(path) os.mkdir(os.path.join(path, 'EGG-INFO')) pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') f = open(pkg_info, 'w') try: f.write(SETUPTOOLS_PKG_INFO) finally: f.close() return True _patch_egg_dir = _no_sandbox(_patch_egg_dir) def _before_install(): log.warn('Before install bootstrap.') _fake_setuptools() def _under_prefix(location): if 'install' not in sys.argv: return True args = sys.argv[sys.argv.index('install') + 1:] for index, arg in enumerate(args): for option in ('--root', '--prefix'): if arg.startswith('%s=' % option): top_dir = arg.split('root=')[-1] return location.startswith(top_dir) elif arg == option: if len(args) > index: top_dir = args[index + 1] return location.startswith(top_dir) if arg == '--user' and USER_SITE is not None: return location.startswith(USER_SITE) return True def _fake_setuptools(): log.warn('Scanning installed packages') try: import pkg_resources except ImportError: # we're cool log.warn('Setuptools or Distribute does not seem to be installed.') return ws = pkg_resources.working_set try: setuptools_dist = ws.find( pkg_resources.Requirement.parse('setuptools', replacement=False) ) except TypeError: # old distribute API setuptools_dist = ws.find( pkg_resources.Requirement.parse('setuptools') ) if setuptools_dist is None: log.warn('No setuptools distribution found') return # detecting if it was already faked setuptools_location = setuptools_dist.location log.warn('Setuptools installation detected at %s', setuptools_location) # if --root or --preix was provided, and if # setuptools is not located in them, we don't patch it if not _under_prefix(setuptools_location): log.warn('Not patching, --root or --prefix is installing Distribute' ' in another location') return # let's see if its an egg if not setuptools_location.endswith('.egg'): log.warn('Non-egg installation') res = _remove_flat_installation(setuptools_location) if not res: return else: log.warn('Egg installation') pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') if (os.path.exists(pkg_info) and _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): log.warn('Already patched.') return log.warn('Patching...') # let's create a fake egg replacing setuptools one res = _patch_egg_dir(setuptools_location) if not res: return log.warn('Patching complete.') _relaunch() def _relaunch(): log.warn('Relaunching...') # we have to relaunch the process # pip marker to avoid a relaunch bug _cmd1 = ['-c', 'install', '--single-version-externally-managed'] _cmd2 = ['-c', 'install', '--record'] if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: sys.argv[0] = 'setup.py' args = [sys.executable] + sys.argv sys.exit(subprocess.call(args)) def _extractall(self, path=".", members=None): """Extract all members from the archive to the current working directory and set owner, modification time and permissions on directories afterwards. `path' specifies a different directory to extract to. `members' is optional and must be a subset of the list returned by getmembers(). """ import copy import operator from tarfile import ExtractError directories = [] if members is None: members = self for tarinfo in members: if tarinfo.isdir(): # Extract directories with a safe mode. directories.append(tarinfo) tarinfo = copy.copy(tarinfo) tarinfo.mode = 448 # decimal for oct 0700 self.extract(tarinfo, path) # Reverse sort directories. if sys.version_info < (2, 4): def sorter(dir1, dir2): return cmp(dir1.name, dir2.name) directories.sort(sorter) directories.reverse() else: directories.sort(key=operator.attrgetter('name'), reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: dirpath = os.path.join(path, tarinfo.name) try: self.chown(tarinfo, dirpath) self.utime(tarinfo, dirpath) self.chmod(tarinfo, dirpath) except ExtractError: e = sys.exc_info()[1] if self.errorlevel > 1: raise else: self._dbg(1, "tarfile: %s" % e) def _build_install_args(options): """ Build the arguments to 'python setup.py install' on the distribute package """ install_args = [] if options.user_install: if sys.version_info < (2, 6): log.warn("--user requires Python 2.6 or later") raise SystemExit(1) install_args.append('--user') return install_args def _parse_args(): """ Parse the command line for options """ parser = optparse.OptionParser() parser.add_option( '--user', dest='user_install', action='store_true', default=False, help='install in user site package (requires Python 2.6 or later)') parser.add_option( '--download-base', dest='download_base', metavar="URL", default=DEFAULT_URL, help='alternative URL from where to download the distribute package') options, args = parser.parse_args() # positional arguments are ignored return options def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() tarball = download_setuptools(download_base=options.download_base) return _install(tarball, _build_install_args(options)) if __name__ == '__main__': sys.exit(main()) spyne-spyne-2.14.0/bin/sort_wsdl.py000077500000000000000000000002351417664205300172450ustar00rootroot00000000000000#!/usr/bin/env python import sys try: import _preamble except ImportError: sys.exc_clear() from spyne.test.sort_wsdl import main sys.exit(main()) spyne-spyne-2.14.0/doc/000077500000000000000000000000001417664205300146455ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/Makefile000066400000000000000000000107561417664205300163160ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/spyne.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/spyne.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/spyne" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/spyne" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." spyne-spyne-2.14.0/doc/gendoc.sh000077500000000000000000000004451417664205300164460ustar00rootroot00000000000000#!/bin/sh -x git branch rm -rf docs; declare -A docs=( [2.10]=2_10 [2.11]=2_11 [2.12]=master ); for i in ${!docs[@]}; do git checkout ${docs[$i]} || exit 1; rm -rf $i; make clean; make html || exit 1; mv build/html $i; done; mkdir docs; mv ${!docs[@]} docs make clean; spyne-spyne-2.14.0/doc/make.bat000066400000000000000000000106411417664205300162540ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\spyne.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\spyne.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end spyne-spyne-2.14.0/doc/source/000077500000000000000000000000001417664205300161455ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/.static/000077500000000000000000000000001417664205300175125ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/.static/placeholder.txt000066400000000000000000000001351417664205300225340ustar00rootroot00000000000000placeholder file because Git won't allow an empty dir in a repo, but Sphinx expects this dir spyne-spyne-2.14.0/doc/source/.templates/000077500000000000000000000000001417664205300202215ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/.templates/placeholder.txt000066400000000000000000000001351417664205300232430ustar00rootroot00000000000000placeholder file because Git won't allow an empty dir in a repo, but Sphinx expects this dir spyne-spyne-2.14.0/doc/source/conf.py000066400000000000000000000206351417664205300174520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # spyne documentation build configuration file, created by # sphinx-quickstart on Mon Sep 12 13:48:26 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import spyne # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. sys.path.append(os.path.abspath('../../examples/django')) os.environ['DJANGO_SETTINGS_MODULE'] = 'rpctest.settings' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinxtogithub', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'spyne' copyright = u'Spyne Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = spyne.__version__ # The full version, including alpha/beta/rc tags. release = spyne.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['.static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'spynedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'spyne.tex', u'spyne Documentation', u'Spyne Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'spyne', u'spyne Documentation', [u'Spyne Contributors'], 1) ] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'spyne' epub_author = u'Spyne Contributors' epub_publisher = u'Spyne Contributors' epub_copyright = u'Spyne Contributors' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} spyne-spyne-2.14.0/doc/source/contributing.rst000066400000000000000000000000701417664205300214030ustar00rootroot00000000000000 .. _contributing: .. include:: ../../CONTRIBUTING.rst spyne-spyne-2.14.0/doc/source/faq.rst000066400000000000000000000300571417664205300174530ustar00rootroot00000000000000 ********* Spyne FAQ ********* Frequently asked questions about Spyne and related libraries. Does spyne support the SOAP 1.2 standard? ========================================= **Short answer:** No. **Long answer:** Nope. Patches are welcome. How do I implement a predefined WSDL? ===================================== **Short answer:** By hand. **Long answer:** Some work has been done towards parsing Xml Schema 1.0 documents and generating Spyne classes from Xml Schema types but it's still in pre-alpha stage. Have a look at ``parser`` and ``genpy`` modules in the ``spyne.interface.xml_schema`` packages. Get in touch for more information. Needless to say, patches are welcome. How do I use variable names that are also Python keywords? ========================================================== Due to restrictions of the python language, you can't do this: :: class SomeClass(ComplexModel): and = String or = Integer import = Datetime The workaround is as follows: :: class SomeClass(ComplexModel): _type_info = { 'and': String 'or': Integer 'import': Datetime } You also can't do this: :: @rpc(String, String, String, _returns=String) def f(ctx, from, import): return '1234' The workaround is as follows: :: @rpc(String, String, String, _returns=String, _in_variable_names={'from_': 'from', 'import_': 'import'}, _out_variable_name="return" def f(ctx, from_, import_): return '1234' See here: https://github.com/arskom/spyne/blob/rpclib-2.5.0-beta/src/rpclib/test/test_service.py#L114 How does Spyne behave in a multi-threaded environment? ====================================================== Spyne code is mostly re-entrant, thus thread safe. Whatever global state that is accessed is initialized and frozen (by convention) before any rpc processing is performed. Some data (like the WSDL document) is initialized on first request, which does need precautions against race conditions. These precautions should be taken in the transport implementations. It's the transport's job to assure thread-safety when accessing any out-of-thread data. No other parts of Spyne should be made aware of threads. What implications does Spyne's license (LGPL) have for proprietary projects that use it? ======================================================================================== DISCLAIMER: This is not legal advice, but just how we think things should work. **Short Answer:** As long as you don't modify Spyne itself, you can freely use Spyne in conjunction with your proprietary code, without any additional obligations. **Long Answer:** If you make modifications to Spyne, the best thing to do is to put them on github and just send a pull request upstream. Even if your patch is not accepted, you've done more than what the license requires you to do. If you make modifications to Spyne and deploy a modified version to your client's site, the minimum you should do is to pass along the source code for the modified Spyne to your clients. Again, you can just put your modifications up somewhere, or better, send them to the Spyne maintainers, but if for some reason (we can't imagine any, to be honest) you can't do this, your obligation is to have your client have the source code with your modifications. The thing to watch out for when distributing a modified Spyne version as part of your proprieatry solution is to make sure that Spyne runs just fine by itself without needing your code. Again, this will be the case if you did not touch Spyne code itself. If your modifications to Spyne make it somehow dependant on your software, you must pass your modifications as well as the code that Spyne needs to the people who deploy your solution. In other words, if your code and Spyne is tightly coupled, the license of Spyne propagates to your code as well. Spyne is a descendant of Soaplib, which was published by its author initially under LGPL. When he quit, the people who took over contemplated re-licensing it under the three-clause BSD license, but were not able to reach the original author. A re-licensing is even less probable today because of the number of people who've contributed code in the past years as we'd need to get the approval of every single person in order to re-license Spyne. It's also not possible to distribute Spyne under a dual license model for the same reason -- everybody would have to approve the new licensing terms. My app freezes under mod_wsgi! Help! ==================================== **Short answer:** Add this to the relevant fragment of your Apache configuration: :: WSGIApplicationGroup %{GLOBAL} **Long answer:** See here: https://techknowhow.library.emory.edu/blogs/branker/2010/07/30/django-lxml-wsgi-and-python-sub-interpreter-magic Copying the blog post here in case the original link disappears: **[Django] lxml, WSGI, and Python sub-interpreter magic** *Posted Fri, 07/30/2010 - 18:21 by branker* One of the applications we’ve been spending a fair chunk of time on here in the library is a user-friendly front-end to our fedora repository. It’s built on internally-developed Python libraries for repository access, XML data mapping, and Django tie-ins. We’re aiming to opensource that library soon, but this post isn’t about that library. In fact, it’s only sort of about the application. This post is about an interesting problem we ran into this week when trying to deploy that application into our staging environment for testing. See, we’ve made some great strides with development, and we’re ready to put them up so that our users—mostly our own librarians for now—can test them. Development has progressed smoothly under Django’s manage.py runserver. The other day, though, when we ran our application under apache, it surprised us by locking up hard. Now, I can’t think of the last time I saw an http daemon freeze up like that, but it was clear that’s what was happening. The web request wasn’t returning anything (not even a 500 Internal Server Error). Browsers just sat there spinning. curl sat waiting for a response. And eventually apache would give up and drop the connection. It was dead at the starting bell, and with no prior warning of any problems in development. We were confounded. Debugging was an interesting experience, and I hope to post sometime about how that progressed. In the end, though, we figured out it was a design decision that made it happen. Here are the players in this drama: lxml is a fine XML processing library for Python. We use it to process XML as we communicate with fedora. We particularly picked it because it supports XPath expressions, XSLT, and XML Schema, and because it’s pretty darn portable with minimal fuss. Cython is a tool for gluing together C and Python. I started using a variant called Pyrex several years ago, and I happen to think the approach is a great one. lxml happens to use Cython internally. Most users will never need to know that fact, but it becomes relevant in a bit. Django is our web development framework of choice these days at Emory Libraries. It’s written in Python, which has given us a huge dose of flexibility, stability, and power in our development. mod_wsgi is how we deploy our Django code to production. There are other options, but we’ve found WSGI gives us the best mix of flexibility and stability so far. Unfortunately, it was a combination of design decisions in those tools— particularly Cython, Python, and WSGI—that locked up our app. The problem, it turns out, is subtle, but it stems from the use of Cython (via lxml) and mod_wsgi together. These can be made to work together, but it requires careful configuration to work around some incompatibilities. This is complicated by some further design decisions in Django, which I’ll say more about in a bit. First, lxml, Cython, and the simplified GIL interface. Cython, as mentioned above, is a tool for gluing together C and Python. The idea is you write code that looks a lot like Python, but with a few C-like warts, and Cython compiles your code down to raw C. This is perfect for exposing C libraries in Pythonic idioms, and lxml uses it to great effect to provide its XML access. Now, Cython happens to use Python’s simplified GIL interface internally for locking. Unfortunately this means that it’s incompatible with an obscure Python feature called sub-interpreters. Most applications don’t need to use this feature. Most applications—notably including Django’s manage.py runserver—never notice or care. mod_wsgi is a perfect example of good use of sub-interpreters. It uses them to allow apache admins to run lots of little WSGI-based web apps all in a single process, but still give each one its own Python environment. Without this, things like Django’s model registration patterns—along with similar global systems in many other Python libraries—would leave separate applications all interfering with each other. Unfortunately, given that Cython-based libraries are incompatible with sub- interpreters, and given that mod_wsgi uses sub-interpreters, it follows logically that Cython-based libraries like lxml are incompatible with simple mod_wsgi configurations. In our case, this manifested as a single- thread self-deadlock in the Python Global Interpreter Lock whenever we tried to use our application at all. We were lucky: As the Python C-API docs say, “Simple things may work, but confusing behavior will always be near.” Now, once that incompatibility is recognized and accepted, hope is not lost. If you’re only running a single WSGI application, your workaround might even be easy. You can force a mod_wsgi application to avoid the problem by forcing it into the global application group: WSGIApplicationGroup %{GLOBAL} If you want to run multiple WSGI applications, though, they might not play so well all together like that. Remember, as I described above, WSGI uses sub-interpreters to prevent applications from accidentally stepping on each other. Django applications, in particular, must run in separate sub- interpreters. If you want to run a couple of them, and they’re all incompatible with sub-interpreters, you need to keep them separate. We’re just starting to deal with this problem, but it looks like mod_wsgi daemon processes are just what the doctor ordered. What we’re looking at right now is using a separate WSGIDaemonProcess for each lxml-enabled Django site. According to the docs, this should eliminate sub-interpreter conflicts while still giving each application its own distinct interpreter space. Which will probably eat some system resources, but it’s better than locking up on every request. I’ll update this post if the strategy turns out not to work. So far, though, I’m hopeful. My logs are full of 'Unicode strings with encoding declaration are not supported' messages. Should I be worried? ================================================================================================================ Apparently some WSGI implementations hand a ``unicode`` instance to Wsgi applications instead of a ``str``\. lxml either wants a ``str`` with encoding declaration or a ``unicode`` without one and snobbishly refuses to cooperate otherwise. See http://lxml.de/parsing.html#python-unicode-strings for more info. If your WSGI implementation hands you a ``unicode``, it's inefficient. That's because it wastes time converting the incoming byte stream to unicode, an operation that may or may not be necessary. The decision whether to perform the ``str`` => ``unicode`` conversion should be left to the protocol. You should get rid of this conversion operation -- that's why that warning is there. It is otherwise harmless. You mock my pain! ================= Life is pain, highness. Anyone who says differently is selling something. spyne-spyne-2.14.0/doc/source/history.rst000066400000000000000000000612321417664205300204040ustar00rootroot00000000000000 .. _changelog: ******************* Development History ******************* Versioning ========== Spyne respects the "Semantic Versioning" rules outlined in http://semver.org. This means you can do better than just adding ``'spyne'`` to your list of dependencies. Assuming the current version of spyne is 2.4.8, you can use the following as dependency string: * ``spyne`` if you feel adventurous or are willing to work on spyne itself. * ``spyne<2.99`` if you only want backwards-compatible bug fixes and new features. * ``spyne<2.4.99`` if you only want backwards-compatible bug fixes. * ``spyne=2.4.8`` if you rather like that version. We have a policy of pushing to pypi as soon as possible, so be sure to at least use the second option to prevent things from breaking unexpectedly. It's recommended to use the third option in production and only upgrade after you've tested any new version in your staging environment. While we make every effort to keep to our compatibility promise, Spyne is a fast moving open-source project that may break in ways that can't be cought by our test suite between feature releases. Spyne project uses -alpha -beta and -rc labels to denote unfinished code. We don't prefer using separate integers for experimental labels. So for example, instead of having 2.0.0-alpha47, we'll have 2.2.5-alpha. * **-alpha** means unstable code. You may not recognize the project next time you look at it. * **-beta** means stable(ish) api, unstable behavior, there are bugs everywhere! Don't be upset if some quirks that you rely on disappear. * **-rc** means it's been working in production sans issues for some time on beta-testers' sites, but we'd still like it to have tested by a few more people. These labels apply to the project as a whole. Thus, we won't tag the whole project as beta because some new feature is not yet well-tested, but we will clearly denote experimental code in its documentation. .. include:: ../../CHANGELOG.rst spyne-2.10.10 ------------- * Fix wsdl rendering in TwistedWebResource. * Fix http response header propagation in TwistedWebResource. * Fix handling of fractions in microsecond values. * Fix spyne.util.get_validation_schema() spyne-2.10.9 ------------ * Fix total_seconds quirk for Python 2.6. * Turn off Xml features like entity resolution by default. This mitigates an information disclosure attack risk in services whose response contain some fragments or all of the request. Also prevents DoS attacks that make use of entity expansion. See https://bitbucket.org/tiran/defusedxml for more info. * Drop Python 2.5 support (It wasn't working anyway). spyne-2.10.8 ------------ * Fix Unicode losing pattern on re-customization * Fix Duration serialization, add a ton of test cases. * Fix binary urlsafe_base64 encoding. * Fix arbitrary exception serialization. * Fix some doc errors. spyne-2.10.7 ------------ * Fix logic error in wsdl caching that prevented the url in Wsdl document from being customized. * Fix dictdoc not playing well with functions with empty return values. spyne-2.10.6 ------------ * Fix exception serialization regression in DictDocument family. * Fix xml utils (and its example). spyne-2.10.5 ------------ * Fix default value handling in ``HttpRpc``. * Fix invalid document type raising ``InternalError`` in DictDocument family. It now raises ``ValidationError``. * HttpRpc: Fix ``ByteArray`` deserialization. * HttpRpc: Fix many corner cases with ``Array``\s. * Fix Csv serializer. * Fix Mandatory variants of ``Double`` and ``Float`` inheriting from decimal. spyne-2.10.4 ------------ * Fix handling of ``spyne.model.binary.File.Value`` with just path name. * Fix decimal restrictions (some more). * Make user code that doesn't return anything work with twisted server transport. spyne-2.10.3 ------------ * Add validation tests for HierDictDocument and fix seen issues. * Add validation tests for FlatDictDocument and fix seen issues. * Clarify Json and Http behavior in relevant docstrings. * Fix Python2.6 generating max_occurs="inf" instead of "unbounded" sometimes. spyne-2.10.2 ------------ * Fix ByteArray support accross all protocols. * Fix namespaces of customized simple types inside ``XmlAttribute`` not being imported. spyne-2.10.1 ------------ * Fix confusion in Decimal restriction assignment. * Fix classmethod calls to ProtocolBase. * Fix schema generation error in namespaced xml attribute case. spyne-2.10.0 ------------ * Returning twisted's Deferred from user code is now supported. * You can now set Http response headers via ctx.out_header when out_protocol is HttpRpc. https://github.com/arskom/spyne/pull/201 * lxml is not a hard requirement anymore. * XmlDocument and friends: cleanup_namespaces is now True by default. * XmlDocument and friends: Added ``encoding`` and ``pretty_print`` flags that are directly passed to ``lxml.etree.tostring()``. * XmlDocument and friends:'attribute_of' added to ModelBase to add attribute support for primitives. This is currently ignored by (and mostly irrelevant to) other protocols. * XmlDocument and friends: Attribute serialization is working for arrays. * Add support for exposing existing whose source code via the _args argument to the srpc decorator. See the existing_api example for usage examples. * Add Streaming versions of Pyramid and Django bridge objects. * Remove destructor from ``MethodContext``. Now transports need to call ``.close()`` explicitly to close object and fire relevant events. * Application event 'method_context_constructed' was renamed to ``'method_context_created'``. * Application event 'method_context_destroyed' was removed. The ``'method_context_closed'`` event can be used instead. * SQLAlchemy integration now supports advanced features like specifying indexing methods. * The object composition graph can now be cyclic. * Integers were overhauled. Now boundary values of limited-size types are accessible via ``Attributes._{min,max}_bounds``. * We now have six spatial types, ``Point``, ``LineString`` and ``Polygon`` along with their ``Multi*`` variants. * The deprecated ``ProtocolBase.set_method_descriptor`` function was removed. * It's now possible to override serialization in service implementations. You can set ``ctx.out_document`` to have the return value from user funtion ignored. You can also set ``ctx.out_string`` to have the ``ctx.out_document`` ignored as well. * Added as_timezone support to DateTime. It calls ``.astimezone(as_time_zone).replace(tzinfo=None)`` on native values. * Added YAML support via PyYaml. * Split dict logic in DictDocument as ``HierDictDocument`` and ``FlatDictDocument``. * Complete revamp of how DictDocument family work. skip_depth is replaced by richer functionalty that is enabled by two flags: ``ignore_wrappers`` and ``complex_as``. * Added cookie parsing support to HttpRpc via ``Cookie.SimpleCookie``. * Moved ``{to,from}_string`` logic from data models to ProtocolBase. This gives us the ability to have more complex fault messages with other fault subelements that are namespace-qualified without circular dependency problems - Stefan Andersson * DictDocument and friends: ``ignore_wrappers`` and ``complex_as`` options added as a way to customize protocol output without hindering other parts of the interface. spyne-2.9.5 ----------- * Fix restriction bases of simple types not being imported. * Fix for customized subclasses forgetting about their empty base classes. * Fix Attributes.nullable not surviving customization. spyne-2.9.4 ----------- * Fix for Python 2.6 quirk where any ``decimal.Decimal()`` is always less than any ``float()``. Where did that come from?! * Fix missing '/' in WsgiMounter. * Fix confusion in ``spyne.model.primitive.Decimal``'s parameter order. * Add forgotten ``HttpBase`` parameters to ``WsgiApplication``. spyne-2.9.3 ----------- * Fix WsgiApplication choking on empty string return value. * Fix TwistedWebResource choking on generators as return values. * Fix Csv serializer. spyne-2.9.2 ----------- * Fix Array serialization for Html Microformats * Fix deserialization of Fault objects for Soap11 * Fix Uuid not playing well with soft validation. * Fix Uuid not playing well with Xml Schema document. spyne-2.9.0 ----------- * Spyne is now stable! * Fix document_built events by adding a ``doc`` attribute to the ServerBase class. You can now do ``some_server.doc.wsdl11.event_manager.add_listener`` to add events to interface documents. * Add wsdl_document_built and xml_document_built events to relevant classes. * Behavioral change for TableModel's relationship handling: It's now an array by default. The TableModel is deprecated, long live __metadata__ on ComplexModel! * First-class integration with Pyramid. * First geospatial types: Point and Polygon. * Initial revision of the http request pattern matching support via ``werkzeug.routing``. * ``XmlObject`` -> ``XmlDocument``, ``JsonObject`` -> ``JsonDocument``, ``MessagePackObject`` -> ``MessagePackDocument``, ``DictObject`` -> ``DictDocument``. spyne-2.8.2-rc -------------- * travis-ci.org integration! See for yourself: http://travis-ci.org/arskom/spyne * Python 2.4 compatibility claim was dropped, because this particular Python version is nowhere to be found. * Many issues with Python 2.5 compatibility are fixed. spyne-2.8.1-rc -------------- * Misc fixes regarding the spyne.model.binary.File api. rpclib-2.8.0-rc -> spyne-2.8.0-rc --------------------------------- * Rpclib is dead. Long live Spyne! * Add support for JsonObject protocol. This initial version is expremental. * Add support for MessagePackObject and MessagePackRpc protocols. These initial versions are expremental. * Make DateTime string format customizable. * Implement TwistedWebResource that exposes an ``Application`` instance as a ``twisted.web.resource.Resource`` child. * Remove Deprecated ``XMLAttribute`` and ``XMLAttributeRef``. Use ``XmlAttribute`` and ``XmlAttributeRef`` instead. * Xml Schema: Add support for the tag. * Add a chapter about Validation to the manual. Thanks Alex! * Interface documents are no longer subclasses of InterfaceBase. It's up to the transport to expose the application using a given interface document standard now. The ``interface`` argument to the ``Application`` constructor is now ignored. * Html: Added a very simple lxml-based templating scheme: ``HtmlPage``. * Html: Added row-based tables: They show fields in rows. It's good for showing one object per table. * Html: Added ImageUri support. They render as tags in Html output. * Html: Added support for locales. You can now render field names as human- readable strings. * Add support for async methods, which execute after the primary user code returns. Currently, the only async execution method is via threads. * Xml & friends: Start tags are now in the same namespace as the definitions themselves. Intermediate tags are in the parent's namespace, just as before. * Xml & friends: Make the 'bare' mode work. * spyne.util.xml: ``get_object_as_xml`` can also get class suggestion. * spyne.util.xml: ``get_xml_as_object`` has argument order swapped: cls, elt -> elt, cls. See ab91a3e2ad4756b71d1a2752e5b0d2af8551e061. * There's a final argument order change in Application ctor: in_protocol, out_protocol, interface, name becomes: name, in_protocol, out_protocol, interface * Relevant pull requests with new features and notable changes: * https://github.com/arskom/spyne/pull/128 * https://github.com/arskom/spyne/pull/129 * https://github.com/arskom/spyne/pull/139 * https://github.com/arskom/spyne/pull/142 * https://github.com/arskom/spyne/pull/148 * https://github.com/arskom/spyne/pull/157 * https://github.com/arskom/spyne/pull/173 rpclib-2.7.0-beta ----------------- * Add support for non-chunked encoding to Wsgi transport. * Add support for Html Microformats. * Add ``function`` property to MethodContext that is re-initialized from ``descriptor.function`` for each new request. Stay away from ``descriptor.function`` unless you understand the consequences!.. * String and Unicode models are now separate objects with well-defined (de)serialization behaviour. * Argument order change in Application ctor: :: interface, in_protocol, out_protocol becomes: :: in_protocol, out_protocol, interface See here: https://github.com/arskom/spyne/commit/45f5af70aa826640008222bda96299d51c9df980#diff-1 * Full changelog: * https://github.com/arskom/spyne/pull/123 * https://github.com/arskom/spyne/pull/124 * https://github.com/arskom/spyne/pull/125 rpclib-2.6.1-beta ----------------- * Fix (for real this time) the race condition in wsgi server's wsdl handler. rpclib-2.6.0-beta ----------------- * HttpRpc now parses POST/PUT/PATCH bodies, can accept file uploads. Uses werkzeug to do that, which is now a soft dependency. * ByteArray now child of SimpleModel. It's now possible to customize it simply by calling it. * Fix race condition in wsgi server wsdl request. * Full change log: https://github.com/arskom/spyne/pull/122 rpclib-2.5.2-beta ----------------- * Misc. fixes. * Full change log: https://github.com/arskom/spyne/pull/118 rpclib-2.5.1-beta ----------------- * Switched to magic cookie constants instead of strings in protocol logic. * check_validator -> set_validator in ProtocolBase * Started parsing Http headers in HttpRpc protocol. * HttpRpc now properly validates nested value frequencies. * HttpRpc now works with arrays of simple types as well. * Full change log: https://github.com/arskom/spyne/pull/117 https://github.com/arskom/spyne/pull/116 rpclib-2.5.0-beta ----------------- * Implemented fanout support for transports and protocols that can handle that. * Implemented a helper module that generates a Soap/Wsdl 1.1 application in ``rpclib.util.simple`` * Some work towards supporting Python3 using ``2to3``. See issue #113. * ``ctx.descriptor.reset_function`` implemented. It's now safe to fiddle with that value in event handlers. * Added a cleaned-up version of the Django wrapper: https://gist.github.com/1316025 * Fix most of the tests that fail due to api changes. * Fix Http soap client. * Full change log: https://github.com/arskom/spyne/pull/115 rpclib-2.4.7-beta ----------------- * Made color in logs optional * Fixed ByteArray serializer rpclib-2.4.5-beta ----------------- * Time primitive was implemented. * Fix for multiple ports was integrated. * Added http cookie authentication example with suds. * Full change log: https://github.com/arskom/spyne/pull/109 rpclib-2.4.3-beta ----------------- * Many issues with 'soft' validation was fixed. * ``MethodDescriptor.udp`` added. Short for "User-Defined Properties", you can use it to store arbitrary metadata about the decorated method. * Fix HttpRpc response serialization. * Documentation updates. rpclib-2.4.1-beta ----------------- * Fixed import errors in Python<=2.5. * A problem with rpclib's String and unicode objects was fixed. rpclib-2.4.0-beta ----------------- * Fixed Fault publishing in Wsdl. * Implemented 'soft' validation. * Documentation improvements. It's mostly ready! * A bug with min/max_occurs logic was fixed. This causes rpclib not to send null values for elements with min_occurs=0 (the default value). * Native value for ``rpclib.model.primitive.String`` was changed to ``unicode``. To exchange raw data, you should use ``rpclib.model.binary.ByteArray``. * Full change log: https://github.com/arskom/spyne/pull/90 rpclib-2.3.3-beta ----------------- * Added MAX_CONTENT_LENGTH = 2 * 1024 * 1024 and BLOCK_LENGTH = 8 * 1024 constants to rpclib.server.wsgi module. * rpclib.model.binary.Attachment is deprecated, and is replaced by ByteArray. The native format of ByteArray is an iterable of strings. * Exception handling was formalized. HTTP return codes can be set by exception classes from rpclib.error or custom exceptions. * Full change log: https://github.com/arskom/spyne/pull/88 rpclib-2.3.2-beta ----------------- * Limited support for sqlalchemy.orm.relationship (no string arguments) * Added missing event firings. * Documented event api and fundamental data structures (rpclib._base) * Full change log: https://github.com/arskom/spyne/pull/87 rpclib-2.3.1-beta ----------------- * HttpRpc protocol now returns 404 when a requested resource was not found. * New tests added for HttpRpc protocol. * Miscellanous other fixes. See: https://github.com/arskom/spyne/pull/86 rpclib-2.3.0-beta ----------------- * Documentation improvements. * rpclib.protocol.xml.XmlObject is now working as out_protocol. * Many fixes. rpclib-2.2.3-beta ----------------- * Documentation improvements. * rpclib.client.http.Client -> rpclib.client.http.HttpClient * rpclib.client.zeromq.Client -> rpclib.client.zeromq.ZeroMQClient * rpclib.server.zeromq.Server -> rpclib.server.zeromq.ZeroMQServer * rpclib.model.table.TableSerializer -> rpclib.model.table.TableModel rpclib-2.2.2-beta ----------------- * Fixed call to rpclib.application.Application.call_wrapper * Fixed HttpRpc server transport instantiation. * Documentation improvements. rpclib-2.2.1-beta ----------------- * rpclib.application.Application.call_wrapper introduced * Documentation improvements. rpclib-2.2.0-beta ----------------- * The serialization / deserialization logic was redesigned. Now most of the serialization-related logic is under the responsibility of the ProtocolBase children. * Interface generation logic was redesigned. The WSDL logic is separated to XmlSchema and Wsdl11 classes. 'add_to_schema' calls were renamed to just 'add' and were moved inside rpclib.interface.xml_schema package. * Interface and Protocol assignment of an rpclib application is now more explicit. Both are also configurable during instantion. This doesn't mean there's much to configure :) * WS-I Conformance is back!. See https://github.com/arskom/spyne/blob/master/src/rpclib/test/interop/wsi-report-rpclib.xml for the latest conformance report. * Numeric types now support range restrictions. e.g. Integer(ge=0) will only accept positive integers. * Any -> AnyXml, AnyAsDict -> AnyDict. AnyAsDict is not the child of the AnyXml anymore. * rpclib.model.exception -> rpclib.model.fault. rpclib-2.1.0-alpha ------------------ * The method dispatch logic was rewritten: It's now possible for the protocols to override how method request strings are matched to methods definitions. * Unsigned integer primitives were added. * ZeroMQ client was fixed. * Header confusion in native http soap client was fixed. * Grouped transport-specific context information under ctx.transport attribute. * Added a self reference mechanism. rpclib-2.0.10-alpha ------------------- * The inclusion of base xml schemas were made optional. * WSDL: Fix out header being the same as in header. * Added type checking to outgoing Integer types. it's not handled as nicely as it should be. * Fixed the case where changing the _in_message tag name of the method prevented it from being called. * SOAP/WSDL: Added support for multiple {in,out}_header objects. * Fix some XMLAttribute bugs. rpclib-2.0.9-alpha ------------------ * Added inheritance support to rpclib.model.table.TableSerializer. rpclib-2.0.8-alpha ------------------ * The NullServer now also returns context with the return object to have it survive past user-defined method return. rpclib-2.0.7-alpha ------------------ * More tests are migrated to the new api. * Function identifier strings are no more created directly from the function object itself. Function's key in the class definition is used as default instead. * Base xml schemas are no longer imported. rpclib-2.0.6-alpha ------------------ * Added rpclib.server.null.NullServer, which is a server class with a client interface that attempts to do no (de)serialization at all. It's intended to be used in tests. rpclib-2.0.5-alpha ------------------ * Add late mapping support to sqlalchemy table serializer. rpclib-2.0.4-alpha ------------------ * Add preliminary support for a sqlalchemy-0.7-compatible serializer. rpclib-2.0.3-alpha ------------------ * Migrate the HttpRpc serializer to the new internal api. rpclib-2.0.2-alpha ------------------ * SimpleType -> SimpleModel * Small bugfixes. rpclib-2.0.1-alpha ------------------ * EventManager now uses ordered sets instead of normal sets to store event handlers. * Implemented sort_wsdl, a small hack to sort wsdl output in order to ease debugging. rpclib-2.0.0-alpha ------------------ * Implemented EventManager and replaced hook calls with events. * The rpc decorator now produces static methods. The methods still get an implicit first argument that holds the service contexts. It's an instance of the MethodContext class, and not the ServiceBase (formerly DefinitionBase) class. * The new srpc decorator doesn't force the methods to have an implicit first argument. * Fixed fault namespace resolution. * Moved xml constants to rpclib.const.xml_ns * The following changes to soaplib were ported to rpclib's SOAP/WSDL parts: * duration object is now compatible with Python's native timedelta. * WSDL: Support for multiple tags in the wsdl (one for each class in the application) * WSDL: Support for multiple tags and multiple ports. * WSDL: Support for enumerating exceptions a method can throw was added. * SOAP: Exceptions got some love to be more standards-compliant. * SOAP: Xml attribute support * Moved all modules with packagename.base to packagename._base. * Renamed classes to have module name as a prefix: * rpclib.client._base.Base -> rpclib.client._base.ClientBase * rpclib.model._base.Base -> rpclib.model._base.ModelBase * rpclib.protocol._base.Base -> rpclib.protocol._base.ProtocolBase * rpclib.server._base.Base -> rpclib.server._base.ServerBase * rpclib.service.DefinitionBase -> rpclib.service.ServiceBase * rpclib.server.wsgi.Application -> rpclib.server.wsgi.WsgiApplication * Moved some classes and modules around: * rpclib.model.clazz -> rpclib.model.complex * rpclib.model.complex.ClassSerializer -> rpclib.model.complex.ComplexModel * rpclib.Application -> rpclib.application.Application * rpclib.service.rpc, srpc -> rpclib.decorator.rpc, srpc soaplib-3.x -> rpclib-1.1.1-alpha --------------------------------- * Soaplib is now also protocol agnostic. As it now supports protocols other than soap (like Rest-minus-the-verbs HttpRpc), it's renamed to rpclib. This also means soaplib can now support multiple versions of soap and wsdl standards. * Mention of xml and soap removed from public api where it's not directly related to soap or xml. (e.g. a hook rename: on_method_exception_xml -> on_method_exception_doc) * Protocol serializers now return iterables instead of complete messages. This is a first step towards eliminating the need to have the whole message in memory during processing. soaplib-2.x ----------- * This release transformed soaplib from a soap server that exclusively supported http to a soap serialization/deserialization library that is architecture and transport agnostic. * Hard dependency on WSGI removed. * Sphinx docs with working examples: http://arskom.github.com/soaplib/ * Serializers renamed to Models. * Standalone xsd generation for ClassSerializer objects has been added. This allows soaplib to be used to define generic XML schemas, without SOAP artifacts. * Annotation Tags for primitive Models has been added. * The soaplib client has been re-written after having been dropped from recent releases. It follows the suds API but is based on lxml for better performance. WARNING: the soaplib client is not well-tested and future support is tentative and dependent on community response. * 0mq support added. * Twisted supported via WSGI wrappers. * Increased test coverage for soaplib and supported servers soaplib-1.0 ----------- * Standards-compliant Soap Faults * Allow multiple return values and return types soaplib-0.9.4 ------------- * pritimitive.Array -> clazz.Array * Support for SimpleType restrictions (pattern, length, etc.) soaplib-0.9.3 ------------- * Soap header support * Tried the WS-I Test first time. Many bug fixes. soaplib-0.9.2 ------------- * Support for inheritance. soaplib-0.9.1 ------------- * Support for publishing multiple service classes. soaplib-0.9 ----------- * Soap server logic almost completely rewritten. * Soap client removed in favor of suds. * Object definition api no longer needs a class types: under class definition. * XML Schema validation is supported. * Support for publishing multiple namespaces. (multiple tags in the wsdl) * Support for enumerations. * Application and Service Definition are separated. Application is instantiated on server start, and Service Definition is instantiated for each new request. * @soapmethod -> @rpc soaplib-0.8.1 ------------- * Switched to lxml for proper xml namespace support. soaplib-0.8.0 ------------- * First public stable release. spyne-spyne-2.14.0/doc/source/index.rst000066400000000000000000000027201417664205300200070ustar00rootroot00000000000000 ################### Spyne Documentation ################### Actually, Spyne 2.11 docs are not quite done yet. Spyne 2.11 has been in the making for more than a year and received around 1400 commits on top of the 2.10 branch. So the documentation is a huge TBD at the moment. There's the migration guide, the changelog and that's that. Yet, we tried very hard to keep backwards compatibility. Which means your existing (correct) code should work without any changes with 2.11. Which in turn means you can safely refer to the 2.10 docs, they're still quite relevant. Don't forget to read the migration guide first if you're migrating from 2.10 to 2.11 though, there were still some changes that can break existing code. But most important of it all, Have fun! PS: Feedback about the docs are pure gold to us, simply because the most important thing the Spyne committers lack is an outside perspective to the project. Please do not hesitate to get in touch with us via people@spyne.io PPS: Why did we not publish 2.11 docs verbatim and update it along the way? Because we *hate* broken documentation links. ############### Getting Started ############### .. include:: ../../README.rst ##################### Library Documentation ##################### TBD. In the mean time, check out the 2.10 docs: http://spyne.io/docs/2.10/#library-documentation ############# Other Goodies ############# .. toctree:: :maxdepth: 2 tests faq history migration contributing spyne-spyne-2.14.0/doc/source/manual/000077500000000000000000000000001417664205300174225ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/manual/01_highlevel.rst000066400000000000000000000165001417664205300224250ustar00rootroot00000000000000 .. _manual-highlevel: High-Level Introduction to Spyne ================================= The impatient can just jump to the :ref:`manual-highlevel-nutshell` section, or read a small `presentation `_ that illustrates the following concepts by examples. Concepts -------- The following is a quick introduction to the Spyne way of naming things: * **Protocols:** Protocols define the rules for transmission of structured data. They are subclasses of :class:`spyne.protocol._base.ProtocolBase` class. In an MVC world, you would call them "Views". For example, Spyne implements a subset of the Soap 1.1 protocol. * **Transports:** Transports, also protocols themselves, encapsulate protocol data in their free-form data sections. They are subclasses of either :class:`spyne.client.ClientBase` or :class:`spyne.server.ServerBase` classes. For example, Http is used as a transport for Soap, by tucking a Soap message in the arbitrary byte-stream part of a Http POST request. The same Http is exposed as a protocol via the :class:`spyne.protocol.http.HttpRpc` class to decode Rest-like request parameters. One could use Soap as a transport by tucking a protocol message in its base64-encoded ByteArray container. Transports appear under two packages in Spyne source code: :mod:`spyne.client` and :mod:`spyne.server`. * **Models:** Models are used to define schemas. They are mere magic cookies that contain very little amount of serialization logic. They are subclasses of :class:`spyne.model._base.ModelBase`. Types like ``Unicode``, ``Integer`` or ``ByteArray`` are all models. They reside in the :mod:`spyne.model` package. * **Interface Documents:** Interface documents provide a machine-readable description of the expected input and output of the exposed method calls. Thus, they have pretty much the same purpose as a method signature in a programming language. They are subclasses of :class:`spyne.interface.base.InterfaceDocumentBase`. :mod:`spyne.interface` package is where you can find them. * **Serializers:** Serializers are currently not distinguished in Spyne code. They are the protocol-specific representations of a serialized Python object. They can be anything between an lxml.etree.Element instance to a gzipped byte stream. Apis around pickle, simplejson, YaML and the like that serialize dynamic hieararchies of "dict"s also fall in this category. * **Native Values:** The term "native value" is used to denote Python types that Spyne models correspond to. For example, we say that the native value of :class:`spyne.model.primitive.Unicode` is ``unicode``, or the native value of :class:`spyne.model.binary.ByteArray` is a sequence of ``str`` objects. How your code is wrapped ------------------------ While the information in the previous section gave you an idea about how Spyne code is organized, this section should give you an idea about how *you* should organize your code using the tools provided by Spyne. Before proceeding further, having good idea about the following four terms used throughout Spyne would be very useful: * **User Methods** or **User Code**: User methods are the code that you wrote and decided to use Spyne to expose to the outside world. They are regular Python functions that don't need to use to any specific API or adhere to any specific convention. * **Decorators**: The ``@rpc`` and ``@srpc`` decorators from :mod:`spyne.decorator` module are used to flag methods that will be exposed to the outside world by marking their input and output types, as well as other properties. Functions decorated with ``@srpc`` don't need to have ``ctx`` as their first argument. It is meant to decorate functions that are not under your direct control. In every other case, you should just use the ``@rpc`` decorator. * **Service Definition**: The :class:`spyne.service.Service` is an abstract base class for a service definition, which is the smallest exposable unit in Spyne. You can use one service class per method definition or you can use, say, a service class for read-only or read/write services or you can cram everything into one service class, it's up to you. Service definition classes are suitable for grouping services that have common properties like logging, transaction management and security policy. It's often a good idea to use your own Service subclass where such common operations are added via events instead of using the vanilla ``Service`` class offered by Spyne. * **Application**: The :class:`spyne.application.Application` class is what ties services and protocols together, ready to be wrapped by a transport. It also lets you define events and hooks like you can with the `Service` class, so you can do more general, application-wide customizations like exception management. .. NOTE:: You may know that Spyne is a generalized version of a Soap library called "soaplib". So inevitably, some artifacts of the Soap world creep in from here and there. One of those artifacts is the namespace feature of Xml. There are varying opinions about the usefulness of Xml namespaces, but as we think it generally to be "A Nice Thing", we chose to keep it around. When instantiating the :class:`spyne.application.Application` class, you should also give it a targetNamespace (the ``tns`` argument to its constructor) string and an optional application name (the ``name`` argument to the :class:`Application` constructor), which are used to generally distinguish your application from other applications *in the universe*. While it's conventionally the URL and the name of the class of your application, you can put ``tns="Hogwarts", name="Harry"`` there and just be done with it. Every object in the Spyne world has a name and belongs to a namespace. Public functions (and the implicit :class:`spyne.model.complex.ComplexModel` subclasses that are created for the input and output types of the functions you defined) are forced to be in the tns of the `Application` and have whatever you give them as `public_name` in the :func:`spyne.decorator.rpc` decorator. Spyne-defined types generally belong to a pre-defined namespace by default. User-defined objects have the module name as namespace string and class name as name string by default. .. _manual-highlevel-nutshell: In a nutshell ^^^^^^^^^^^^^ Your code is inside ``@rpc``-wrapped methods in `Service` subclasses. The `Service` subclasses in turn are wrapped by an Application instance. The `Application` instantiation is used to assign input and output protocols to the exposed methods. The `Application` instance is finally wrapped by a client or server transport that takes the responsibility of moving the bits around. In case you'd like to read about how *exactly* your code is wrapped, you can refer to the relevant part in the :ref:`manual-t-and-p` section. What's next? ------------ Now that you have a general idea about how Spyne is supposed to work, let's get coding. You can start by :ref:`manual-helloworld` tutorial right now. spyne-spyne-2.14.0/doc/source/manual/02_helloworld.rst000066400000000000000000000177331417664205300226430ustar00rootroot00000000000000 .. _manual-helloworld: Hello World =========== This example uses the stock simple wsgi webserver to deploy this service. You should probably use a full-fledged server when deploying your service for production purposes. Defining a Spyne Service ------------------------ Here we introduce the fundamental mechanisms Spyne offers to expose your services. The Soap version of this example is available here: http://github.com/arskom/spyne/blob/master/examples/helloworld_soap.py Dissecting this example: Application is the glue between one or more service definitions, interface and protocol choices. :: from spyne.application import Application The ``@srpc`` decorator exposes methods as remote procedure calls and declares the data types it accepts and returns. The 's' prefix is short for 'static' (or stateless, if you will) -- the function receives no implicit arguments. By contrast, the ``@rpc`` decorator passes a :class:`spyne.MethodContext` instance as first argument to the user code. :: from spyne.decorator import srpc :class:`spyne.service.Service` is the base class for all service definitions. :: from spyne.service import Service The names of the needed types for implementing this service should be self-explanatory. :: from spyne.model.complex import Iterable from spyne.model.primitive import UnsignedInteger from spyne.model.primitive import String Our server is going to use HTTP as transport, so we import the ``WsgiApplication`` from the `:mod:`spyne.server.wsgi` module. It's going to wrap the ``Application`` instance. :: from spyne.server.wsgi import WsgiApplication We start by defining our service. The class name will be made public in the wsdl document unless explicitly overridden with `__service_name__` class attribute. :: class HelloWorldService(Service): The ``@srpc`` decorator flags each method as a remote procedure call and defines the types and order of the soap parameters, as well as the type of the return value. This method takes in a string and an integer and returns an iterable of strings, just like that: :: @srpc(String, UnsignedInteger, _returns=Iterable(String)) The method itself has nothing special about it whatsoever. All input variables and return types are standard python objects:: def say_hello(name, times): for i in xrange(times): yield 'Hello, %s' % name When returning an iterable, you can use any type of python iterable. Here, we chose to use generators. Deploying the service using Soap via Wsgi ----------------------------------------- Now that we have defined our service, we are ready to share it with the outside world. We are going to use the ubiquitious Http protocol as transport, using a Wsgi-compliant http server. This example uses Python's stock Wsgi server. Spyne has been tested with several other web servers, yet, any Wsgi-compliant server should work. This is the required import: :: from wsgiref.simple_server import make_server Here, we configure the python logger to show debugging output. We have to specifically enable the debug output from the soap handler because the Xml pretty_printing code should be run only when explicitly enabled for performance reasons. :: if __name__=='__main__': logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) We glue the service definition, input and output protocols under the "targetNamespace" 'spyne.examples.hello.soap': :: app = Application([HelloWorldService], 'spyne.examples.hello.http', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(), ) In this example, the input validator is on, which means e.g. no negative values will be let in for the ``times`` argument of the ``say_hello`` function, because it is marked as ``UnsignedInteger``. For the Soap 1.1 protocol (actually, for any XML-based protocol), the recommended validator is ``'lxml'`` which uses libxml's native schema validator. It's a fast and robust option that won't tolerate the slightest anomaly in the request document. We then wrap the Spyne application with its wsgi wrapper: :: wsgi_app = WsgiApplication(app) We now register the WSGI application as the handler to the wsgi server, and run the http server: :: server = make_server('127.0.0.1', 7789, wsgi_app) print "listening to http://127.0.0.1:7789" print "wsdl is at: http://localhost:7789/?wsdl" server.serve_forever() .. NOTE:: * **Django users:** See django wrapper example: https://github.com/arskom/spyne/blob/master/examples/django * **Twisted users:** See the these examples that illustrate two ways of deploying a Spyne application using Twisted: http://github.com/arskom/spyne/blob/master/examples/twisted Now that the server implementation is done, you can run it. Now it's time to actually make a request to our server to see it working. You can test your service using suds. Suds is a separate project for implementing pure-python soap clients. To learn more visit the project's page: https://fedorahosted.org/suds/. You can simply install it using ``easy_install suds``. So, here's a three-line script that illustrates how you can use suds to test your new Spyne service: :: from suds.client import Client hello_client = Client('http://localhost:8000/?wsdl') print hello_client.service.say_hello("Dave", 5) The script's output would be as follows: :: (stringArray){ string[] = "Hello, Dave", "Hello, Dave", "Hello, Dave", "Hello, Dave", "Hello, Dave", } The corresponding response document would be: :: Hello, Dave Hello, Dave Hello, Dave Hello, Dave Hello, Dave Deploying the service using HttpRpc/Json -------------------------------------------- This time, we will use a Http as request protocol, and Json as response protocol. This example is available here: http://github.com/arskom/spyne/blob/master/examples/helloworld_http.py We will just need to change the Application definition as follows: :: application = Application([HelloWorldService], 'spyne.examples.hello.http', in_protocol=HttpRpc(validator='soft'), out_protocol=JsonDocument(), ) For HttpRpc, the only available validator is ``'soft'``. It is Spyne's own validation engine that works for all protocols that support it (which includes every implementation that comes bundled with Spyne). Same as before, we then wrap the Spyne application with its wsgi wrapper: :: wsgi_app = WsgiApplication(application) We now register the WSGI application as the handler to the wsgi server, and run the http server: :: server = make_server('127.0.0.1', 8000, wsgi_app) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() Once we run our daemon, we can test it using any Http client. Let's try: :: $ curl -s http://localhost:8000/say_hello?name=Dave\×=3 | python -m json.tool [ "Hello, Dave", "Hello, Dave", "Hello, Dave" ] Spyne tries to make it as easy as possible to work with multiple protocols by being as configurable as possible without having to alter user code. What's next? ^^^^^^^^^^^^ Now that you know how to put a simple Spyne service together, let's continue by reading the :ref:`manual-types` tutorial that will walk you through how native Python types and Spyne markers interact. spyne-spyne-2.14.0/doc/source/manual/03_types.rst000066400000000000000000000643021417664205300216270ustar00rootroot00000000000000 .. _manual-types: Spyne Models and Native Python Types ==================================== There are five types of models in Spyne: * **Primitive:** These are basic models that can contain a single value at a time. Primitive types are more-or-less present in any programming language and all of them map to a well-defined Python type. Types like ``Integer``, ``Decimal``, ``Float``, ``String``, ``Date`` are all primitives. See the documentation for :mod:`spyne.model.primitive` for more info. * **Enum:** Custom types that can take only a pre-determined number of values. It's also possible to get enum-like behavior with primitives as well. Enums are defined using the :class:`spyne.model.enum.Enum` class. * **Binary:** Binary types are used to represent arbitrarily-long byte streams. There are two binary types in Spyne: The :class:`spyne.model.binary.ByteArray` and the :class:`spyne.model.binary.File` class. While any sequence of ``str`` instances can be used as ``ByteArray``, the ``File`` only works with a ``File.Value`` instance. Please see the relevant example below for a more thorough explanation. * **Complex:** Complex objects are subclasses of the :class:`spyne.model.complex.ComplexModel` class. They are hierarchical container classes that can contain any type of object. Two well known examples are the :class:`spyne.model.complex.Array` and :class:`spyne.model.complex.Iterable` types. They are just specialized complex objects. * **Fault:** When an exception is thrown from the user code, it's serialized and returned to the client as a :class:`spyne.model.fault.Fault`. If it is not a subclass of ``Fault``, the client will probably see this as an internal error. Some of the most common exceptions that a web service might need to throw can be found in the :mod:`spyne.error` module. Before going into detail about each category of models, we will first talk about an operation that applies to all models: Type Customization. Customization ------------- Model customization is how one adds declarative restrictions and other metadata to a Spyne model. This model metadata is stored in a generic object called ``Attributes``. Every Spyne model has this object as a class attribute. As an example, let's customize the vanilla ``Unicode`` type to accept only valid email strings: :: class EmailString(Unicode): __type_name__ = 'EmailString' class Attributes(Unicode.Attributes): max_len = 128 pattern = '[^@]+@[^@]+' You must consult the reference of the type you want to customize in order to learn about which values it supports for its ``Attributes`` object. As this is a quite verbose way of doing it, Spyne offers an in-line customization mechanism for every type: :: EmailString = Unicode.customize( max_len=128, pattern='[^@]+@[^@]+', type_name='EmailString', ) Here, ``type_name`` is a special argument name that gets assigned to ``__type_name__`` instead of the ``Attributes`` class. Calling simple types directly is a shortcut to their customize method: :: EmailString = Unicode( max_len=128, pattern='[^@]+@[^@]+', type_name='EmailString', ) As restricting the length of a string is very common (not all types have such shortcuts), the length limit can be passed as a positional argument as well: :: EmailString = Unicode(128, pattern='[^@]+@[^@]+', type_name='EmailString', ) It's actually also not strictly necessary (yet highly recommended) to pass a type name: :: EmailString = Unicode(128, pattern='[^@]+@[^@]+') When the ``type_name`` is omitted, Spyne auto-generates a type name for the new custom type basing on the class it's used in. Type customizations can also be anonymously tucked inside other class definitions: :: class User(ComplexModel): user_name = Unicode(64, pattern='[a-z0-9_-]') email_address = Unicode(128, pattern='[^@]+@[^@]+') Do note that calling ``ComplexModel`` subclasses instantitates them. That's why you should use the ``.customize()`` call, or plain old subclassing to customize complex types: :: class MandatoryUser(User): class Attributes(User.Attributes): nullable=False min_occurs=1 or: :: MandatoryUser = User.customize(nullable=False, min_occurs=1) Primitives ---------- Using primitives in functions are very simple. Here are some examples: :: class SomeSampleServices(Service): @srpc(Decimal, Decimal, _returns=Decimal) def exp(x, y): """Exponentiate arbitrary rationals. A very DoS friendly service!""" return x ** y utcnow = srpc(_returns=DateTime)(datetime.utcnow) @srpc(Unicode, _returns=Unicode) def upper(s): return s.upper() # etc. Let's now look at them group by group: Numbers ^^^^^^^ Numbers are organized in a hierarchy, with the :class:`spyne.model.primitive.Decimal` type at the top. In its vanilla state, the ``Decimal`` class is the arbitrary-precision, arbitrary-size generic number type that will accept just *any* decimal number. The native type is :class:`decimal.Decimal`. It has two direct subclasses: The arbitrary-size :class:`spyne.model.primitive.Integer` type and the machine-dependent :class:`spyne.model.primitive.Double` (:class:`spyne.model.primitive.Float` is a synonym for ``Double`` as Python does not distinguish between floats and doubles) types. Unless you are *sure* that you need to deal with arbitrary-size numbers you should not use the arbitrary-size types in their vanilla form. You must also refrain from using :class:`spyne.model.primitive.Float` and :class:`spyne.model.primitive.Double` types unless you need your math to roll faster as their representation is machine-specific, thus not really reliable nor portable. .. NOTE:: ``float`` and ``decimal.Decimal`` are known to not be getting along too well. That's the case in Spyne as well. The ``Float``/``Double`` and ``Decimal`` markers are NOT compatible. Using ``float`` values in ``Decimal``-denoted fields and vice-versa will cause weird issues because Python's ``float`` values are serialized using ``repr()`` whereas ``Decimal`` is serialized using ``str()``. You have been warned. For integers, we recommend you to use bounded types like :class:`spyne.model.primitive.UnsignedInteger32` which can only contain a 32-bit unsigned integer. (Which is very popular as e.g. a primary key type in a relational database.) For floating-point numbers, use the ``Decimal`` type with a pre-defined scale and precision. E.g. ``Decimal(16, 4)`` can represent a 16-digit number in total which can have up to 4 decimal digits, which could be used e.g. as a nice monetary type. [#]_ Note that it is your responsibility to make sure that the scale and precision constraints are consistent with the values in the context of the decimal package. See the :func:`decimal.getcontext` documentation for more information. It's also possible to set range constraints (``Decimal(gt=4, lt=10)``) or discrete values (``UnsignedInteger8(values=[2,4,6,8]``). Please see the :mod:`spyne.model.primitive` documentation for more details regarding number handling in Spyne. Strings ^^^^^^^ There are two string types in Spyne: :class:`spyne.model.primitive.Unicode` and :class:`spyne.model.primitive.String` whose native types are ``unicode`` and ``str`` respectively. Unlike the Python ``str``, the Spyne ``String`` is not for arbitrary byte streams. You should not use it unless you are absolutely, positively sure that you need to deal with text data with an unknown encoding. In all other cases, you should just use the ``Unicode`` type. They actually look the same from outside, this distinction is made just to properly deal with the quirks surrounding Python-2's ``unicode`` type. Remember that you have the ``ByteArray`` and ``File`` types at your disposal when you need to deal with arbitrary byte streams. The ``String`` type will be just an alias for ``Unicode`` once Spyne gets ported to Python 3. It might even be deprecated and removed in the future, so make sure you are using either ``Unicode`` or ``ByteArray`` in your interface definitions. ``File``, ``ByteArray``, ``Unicode`` and ``String`` are all arbitrary-size in their vanilla versions. Don't forget to customize them with additional restrictions when implementing public services. Just like numbers, it's also possible to place value-based constraints on Strings (e.g. ``String(values=['red', 'green', 'blue'])`` ) but not lexical constraints. See also the configuration parameters of your favorite transport for more information on request size restriction and other precautions against potential abuse. Date/Time Types ^^^^^^^^^^^^^^^ :class:`spyne.model.primitive.Date`, :class:`spyne.model.primitive.Time` and :class:`spyne.model.primitive.DateTime` correspond to the native types ``datetime.date``, ``datetime.time`` and ``datetime.datetime`` respectively. Spyne supports working with both offset-naive and offset-aware datetimes. As long as you return the proper native types, you should be fine. As a side note, the `dateutil `_ package is mighty useful for dealing with dates, times and timezones. Highly recommended! Spatial Types ^^^^^^^^^^^^^ Spyne comes with six basic spatial types that are supported by popular packages like `PostGIS `_ and `Shapely `_. These are provided as ``Unicode`` subclasses that just define proper constraints to force the incoming string to be compliant with the `Well known text (WKT) `_ format. Well known binary (WKB) format is not (yet?) supported. The incoming types are not parsed, but you can use ``shapely.wkb.loads()`` function to convert them to native geometric types. The spatial types that Spyne suppors are as follows: * :class:`spyne.model.primitive.Point` * :class:`spyne.model.primitive.Line` * :class:`spyne.model.primitive.Polygon` Also the ``Multi*`` variants, which are: * :class:`spyne.model.primitive.MultiPoint` * :class:`spyne.model.primitive.MultiLine` * :class:`spyne.model.primitive.MultiPolygon` Miscellanous Types ^^^^^^^^^^^^^^^^^^ There are types defined for convenience in the Xml Schema standard which are just convenience types on top of the text types. They are implemented as they are needed by Spyne users. The following are some of the more notable ones. * :class:`spyne.model.primitive.Boolean`: Life is simple here: Either ``True`` or ``False``. * :class:`spyne.model.primitive.AnyUri`: An RFC-2396 & 2732 compliant URI type. See: http://www.w3.org/TR/xmlschema-2/#anyURI **NOT YET VALIDATED BY SOFT VALIDATION!!!** Patches are welcome :) * :class:`spyne.model.primitive.Uuid`: A fancy way of packing a 128-bit integer. Please consult the :mod:`spyne.model.primitive` documentation for a more complete list. Dynamic Types ^^^^^^^^^^^^^ While Spyne is all about putting firm restrictions on your input schema, it's also about flexibility. That's why, while generally discouraged, the user can choose to accept or return unstructured data using the :class:`spyne.model.primitive.AnyDict`, whose native type is a regular ``dict`` and :class:`spyne.model.primitive.AnyXml` whose native type is a regular :class:`lxml.etree.Element`. ``AnyDict`` and ``AnyXml`` are roughly equivalent when the underlying protocol is an XML based one -- ``AnyDict`` just totally ignores attributes. Mandatory Variants ^^^^^^^^^^^^^^^^^^ TBD Enum ---- The :class:`spyne.model.enum.Enum` type mimics the ``enum`` in C/C++ with some additional type safety. It's part of the Spyne's SOAP heritage so it's mostly for compatibility reasons. If you want to use it, go right ahead, it will work. But you can get the same functionality by defining a custom ``Unicode`` type, like so: :: SomeUnicode = Unicode(values=['x', 'y', 'z']) The equivalent Enum-based declaration would be as follows: :: SomeEnum = Enum('x', 'y', 'z', type_name="SomeEnum") These to would be serialized the same, yet their API is different. Lets look at the following class definition: :: class SomeClass(ComplexModel): a = SomeEnum b = SomeUnicode Assuming the following message comes in: :: x x We will have: :: >>> some_class.a == 'x' True >>> some_class.b == 'x' False >>> some_class.a == SomeEnum.x False >>> some_class.b == SomeEnum.x True >>> some_class.b is SomeEnum.x True So ``Enum`` is just a fancier value-restricted ``Unicode`` that has a marginally faster (as it doesn't do string comparison) comparison option. You probably don't need it. Binary ------ Dealing with binary data has traditionally been a weak spot of most of the serialization formats in use today. The best XML or MIME (email) does is either base64 encoding or something similar, Json has no clue about binary data (and many other things actually, but let's just not go there now) and SOAP, in all its bloatiness, has quite a few binary encoding options available, yet none of the "optimized" ones are implemented in Spyne [#]_. Spyne supports binary data on all of the protocols it implements, falling back to base64 encoding where necessary. In terms of message size, the efficient protocols are `MessagePack `_ and good old Http. But, as MessagePack does not offer an incremental parsing/generation API in its Python wrapper, (in other words, it's not possible to parse the message without having it all in memory) it's best to use the :class:`spyne.protocol.http.HttpRpc` protocol when dealing with arbitrary-size binary data. A few points to consider: 1. ``HttpRpc`` only works with an Http transport. 2. ``HttpRpc`` supports only one file per request. 3. Not every http transport supports incremental parsing of incoming data. (e.g. Twisted). Make sure to test your stack end-to-end to see how it handles huge messages [#]_. Now that all that is said, let's look at the API that Spyne provides for dealing with binary data. Spyne offers two types: 1. :class:`spyne.model.binary.ByteArray` is a simple type that contains arbitrary data. It's similar to Python's own ``str`` in terms of functionality, but it's a sequence of ``str`` instances instead of just a big ``str`` to be able to handle data in chunks using generators when needed [#]_. 2. :class:`spyne.model.binary.File` is a quirkier type that is mainly used to deal with Http way of dealing with file uploads. Its native value is the ``File.Value`` instance in :class:`spyne.model.binary.File`. See its documentation for more information. Dealing with binary data with Spyne is not that hard -- you just need to make sure your data is parsed incrementally when you're preparing to deal with arbitrary-size binary data, which means you need to do careful testing as different WSGI implementations behave differently. Complex ------- Types that can contain other types are termed "complex objects". They must be subclasses of :class:`spyne.model.complex.ComplexModel` class. Here's a sample complex object definition: :: class Permission(ComplexModel): application = Unicode feature = Unicode The ``ComplexModel`` metaclass, namely the :class:`spyne.model.complex.ComplexModelMeta` scans the class definition and ignores attributes that: 1. Begin with an underscore (``_``) 2. Are not subclasses of the ``ModelBase``. If you want to set some defaults (e.g. namespace) with your objects, you can define your own ``ComplexModel`` base class as follows: :: class MyAppComplexModel(ComplexModelBase): __namespace__ = "http://example.com/myapp" __metaclass__ = ComplexModelMeta If you want to use Python keywords as field names, or need leading underscores in field names, or you just want your Spyne definition and other code to be separate, you can do away with the metaclass magic and do this: :: class Permission(ComplexModel): _type_info = { 'application': Unicode, 'feature': Unicode, } However, you still won't get predictable field order, as you're just assigning a ``dict`` to the ``_type_info`` attribute. If you also need that, (which becomes handy when e.g. you serialize your return value directly to HTML, or you need to add fields to your XML messages in a backwards-compatible way [#]_\) you need to pass a sequence of ``(field_name, field_type)`` tuples, like so: :: class Permission(ComplexModel): _type_info = [ ('application', Unicode), ('feature', Unicode), ] This comes with the added bonus of a predictable field order [#]_. A second way of getting a predictable field order is to set the :attr:`spyne.model.complex.ComplexModel.Attributes.declare_order` attribute. The default value of that attribute is going to change in future version of Spyne to ``"name"``. Since the preivous example had the fields sorted by name this will produce the same outcome: :: class PredictableComplexModel(ComplexModelBase): class Attributes(ComplexModelBase.Attributes) declare_order = "name" class Permission(PredictableComplexModel): application = Unicode feature = Unicode Arrays ^^^^^^ If you need to deal with more than one instance of something, the :class:`spyne.model.complex.Array` is what you need. Imagine the following inside the definition of a ``User`` object: :: permissions = Array(Permission) The User can have an infinite number of permissions. If you need to put a limit to that, you can do this: :: permissions = Array(Permission.customize(max_occurs=15)) It is important to emphasize once more that Spyne restrictions are only enforced for an incoming request when validation is enabled. If you want this enforcement for *every* assignment, you do this the usual way by writing a property setter. The ``Array`` type has two alternatives. The first one is the :class:`spyne.model.complex.Iterable` type. :: permissions = Iterable(Permission) It is equivalent to the ``Array`` type from an interface perspective -- the client will not notice any difference between an ``Iterable`` and an ``Array`` as return type. It's just meant to signal the internediate machinery that the return value *could* be a generator and **must not** be consumed unless returning data to the client. This comes in handy for, e.g. custom loggers because they should not try to log the return value (as that would mean consuming the generator). You could use the ``Iterable`` marker in other places instead of ``Array`` without any problems, but it's really meant to be used as return types in function definitions. The second alternative to the ``Array`` notation is the following: :: permissions = Permission.customize(max_occurs='unbounded') The native value that you should return for both remain the same: a sequence of the designated type. However, the exposed interface is slightly different for Xml and friends (other protocols that ship with Spyne always assume the second notation). When you use ``Array``, what really happens is that the ``customize()`` function of the array type creates an in-place class that is equivalent to the following: :: class PermissionArray(ComplexModel): Permission = Permission.customize(max_occurs='unbounded') Whereas when you just set ``max_occurs`` to a value greater than 1, you just get multiple values without the wrapping object. As an example, let's look at the following array: :: v = [ Permission(application='app', feature='f1'), Permission(application='app', feature='f2') ] Here's how it would be serialized to XML with ``Array(Permission)`` as return type: :: app f1 app f2 The same value/type combination would result in the following json document: :: [ { "application": "app", "feature": "f1" }, { "application": "app", "feature": "f2" } ] However, when we serialize the same object to xml using the ``Permission.customize(max_occurs=float('inf'))`` annotation, we get two separate Xml documents, like so: :: app f1 app f2 As for Json, we'd still get the same document. This trick sometimes needed by XML people for interoperability. Otherwise, you can use whichever version pleases your eye the most. You can play with the ``examples/arrays_simple_vs_complex.py`` in the source repository to see the above mechanism at work. Complex Models as Return Values ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When working with functions, you don't need to return instances of the CompexModel subclasses themselves. Anything that walks and quacks like the designated return type will work just fine. Specifically, the returned object should return appropriate values on ``getattr()``\s for field names in the return type. Any exceptions thrown by the object's ``__getattr__`` method will be logged and ignored. However, it is important to return *instances* and not classes themselves. Due to the way Spyne serialization works, the classes themselves will also work as return values until you start seeing funky responses under load in production. Don't do this! [#]_ Fault ----- :class:`spyne.model.fault.Fault` a special kind of ``ComplexModel`` that is also the subclass of Python's own :class:`Exception`. When implementing public Spyne services, the recommendation is to raise instances of ``Fault`` subclasses for client errors, and let other exceptions bubble up until they get logged and re-raised as server-side errors by the protocol handlers. Not all protocols and transports care about distinguishing client and server exceptions. Http has 4xx codes for client-side (invalid request case) errors and 5xx codes for server-side (legitimate request case) errors. SOAP uses "Client." and "Server." prefixes in error codes to make this distinction. To integrate common transport and protocol behavior easily to Spyne, some common exceptions are defined in the :mod:`spyne.error` module. These are then hardwired to some common Http response codes so that e.g. raising a ``ResourceNotFoundError`` ends up setting the response code to 404. You can look at the source code of the :func:`spyne.protocol.ProtocolBase.fault_to_http_response_code` to see which exceptions correspond to which return codes. This can be extended easily by subclassing your transport and overriding the ``fault_to_http_response_code`` function with your own version. Note that, while using an Exception sink to re-raise non-Fault based exceptions as ``InternalError``\s is recommended, it's not Spyne's default behavior -- you need to subclass the :class:`spyne.application.Application` and override the :func:`spyne.application.Application.call_wrapper` function like this: :: class MyApplication(Application): def call_wrapper(self, ctx): try: return ctx.service_class.call_wrapper(ctx) except error.Fault, e: sc = ctx.service_class logger.error("Client Error: %r | Request: %r", (e, ctx.in_object)) raise except Exception, e: sc = ctx.service_class logger.exception(e) raise InternalError(e) What's next? ------------ See the :ref:`manual-user-manager` tutorial that will walk you through defining complex objects and using events. .. [#] By the way, Spyne does not include types like `ISO-4217 `_-compliant 'currency' and 'monetary' types. (See http://www.w3.org/TR/2001/WD-xforms-20010608/slice4.html for more information.) They are actually really easy to implement. If you're looking for a simple way to contribute, this would be a nice place to start! Patches are welcome! .. [#] Spyne used to have mtom (http://www.w3.org/Submission/soap11mtom10/) support. But as it was not maintained in a long time, it's not currently functional. Patches are welcome! .. [#] Not every browser or http daemon supports huge file uploads due to issues around 32-bit integers. E.g. Firefox < 18.0 can't handle big files: https://bugzilla.mozilla.org/show_bug.cgi?id=215450 .. [#] Technically, a simple ``str`` instance is also a sequence of ``str`` instances. However, using a ``str`` as the value to ``ctx.out_string`` would cause sending data in one-byte chunks, which is very inefficient. See e.g. how HTTP's chunked encoding works. .. [#] The "field order" is the order Spyne sends the fields in a ``ComplexModel`` to the client, and the order they are declared in the SOAP WSDL. Currently Spyne's default field order is whatever is returned by a ``dict`` iterator. This can change when the run time environment changes. Things like adding a field, Spyne releasing a new version, or using a different version of the Python interpreter can cause the field order to change. Such a changes are especially painful for SOAP clients because they typically fetch the WSDL once and assume it won't change - often requiring a recompile if it does. Spyne is moving away from it's current unpredicable field order to one controlled by :attr:`spyne.model.complex.ComplexModel.Attributes.declare_order`. It currently defaults to ``"random"`` but this will change in the future. If an unpredictable field order might cause you problems set ``declare_order`` to ``"name"`` or ``"declared"``. .. [#] When you add a new key to a python dict, the entry order can get shuffled. This will make the tag order in your schema change. If for some reason, clients don't see the new schema, they will send documents in old field order. This will make the 'lxml' validator upset as it also validates field older. .. [#] http://stackoverflow.com/a/15383191 spyne-spyne-2.14.0/doc/source/manual/04_usermanager.rst000066400000000000000000000150601417664205300227720ustar00rootroot00000000000000 .. _manual-user-manager: User Manager ============ This tutorial builds on the :ref:`manual-helloworld` and relevant parts of the :ref:`manual-types` tutorial. If you haven't yet done so, we recommended you to read them first. In this tutorial, we will introduce the context object and the events to show how to implement a real-world service. You can see the following code in context in the `examples/user_manager/server_basic.py `_. The following is an event handler that is called on every method call. It instantiates the ``UserDefinedContext`` class and sets it to the context object's ``udc`` attribute, which is in fact short for 'User Defined Context'. :: def _on_method_call(ctx): ctx.udc = UserDefinedContext() We register it as the application's ``'method_call'`` handler. :: application.event_manager.add_listener( 'method_call', _on_method_call) Note that registering it to the service definition's event manager would have the same effect, but it'd have to be set for every other ``Service`` subclass that we'd otherwise define: :: UserManagerService.event_manager.add_listener( 'method_call', _on_method_call) You can also prefer to define your own ``Service`` class and use it as a base class throughout your projects: :: class MyService(Service): pass MyService.event_manager.add_listener('method_call', _on_method_call) Next, we define the UserDefinedContext object. It's just a regular Python class with no specific api it should adhere to: :: class UserDefinedContext(object): def __init__(self): self.users = _user_database @staticmethod def get_next_user_id(): global _user_id_seq _user_id_seq += 1 return _user_id_seq Such custom objects could be used to manage any repetitive task ranging from transactions to logging or to performance measurements. An example on using events to measure method performance can be found in the `examples/events.py `_. Method Metadata --------------- As said before, the smallest exposable unit in Spyne is the Service subclass which has one or more functions decorated with the ``@rpc`` or ``@srpc`` decorator. The ``Service`` subclasses are never instantiated, so methods decorated by ``@rpc`` are implicit ``staticmethod``\s [#]_. The ``@rpc`` decorator is what you would use most of the time. It passes an implicit first argument, the context object, conventionally named ``ctx`` to the user method. The ``@srpc`` decorator is more for functions that you want to expose but have no direct control over. It's useful only for the simplest cases, so when in doubt, you should just use ``@rpc``. The ``@rpc`` decorator takes input types as ``*args`` and other properties as underscore-prefixed ``**kwargs``\. It uses this information and argument names extracted from function source to construct a ``ComplexModel`` object on the fly. Let's look at the following example: :: @rpc(UnsignedByte, DateTime, _returns=Unicode) def some_code(ctx, a_byte, a_date): return "This is what I got: %r %r" % (a_byte, a_date) In the default configuration, the ``@rpc`` decorator creates input and output types behind the scenes as follows: :: class some_code(ComplexModel): # the tns value to the Application constructor __namespace__ = 'application.tns' _type_info = [ ('a_byte', UnsignedByte), ('a_date', DateTime), ] class some_codeResponse(ComplexModel): # the tns value to the Application constructor __namespace__ = 'application.tns' _type_info = [ ('some_codeResult', Unicode), ] You should consult the :func:`spyne.decorator.rpc` reference for more information about various parameters you can pass to tweak how the method is exposed. The ``'Response'`` and ``'Result'`` suffixes are configurable as well. See :mod:`spyne.const` reference for more information. Decorators and ``@rpc`` ^^^^^^^^^^^^^^^^^^^^^^^ Using other decorators with ``@rpc``\-decorated functions is possible, yet a bit tricky. Here's the magic from the :mod:`spyne.decorator`: :: argcount = f.func_code.co_argcount param_names = f.func_code.co_varnames[arg_start:argcount] So if ``f`` here is your decorator, its signature should be the same as the user method, otherwise the parameter names and numbers in the interface are going to be wrong, which will cause weird errors [#]_. This is called "decorator chaining" which is solved by the aptly-named `decorator package `_. Here's an example: :: from decorator import decorator def _do_something(func, *args, **kw): print "before call" result = func(*args, **kw) print "after call" return result def my_decor(f): return decorator(_do_something, f) class tests(Service): @my_decor @srpc(Integer, _returns=Integer) def testf(first): return first Note that the place of the decorator matters. Putting it before ``@srpc`` will make it run once, on service initialization. Putting it after will make it run every time the method is called, but not on initialization. If this looks like too much of a hassle for you, it's also possible to use Spyne events instead of decorators. ``ctx.function`` contains the handle to the original function. You can set that attribute to arbitrary callables to prevent the original user method from running. This property is initiallized from ``ctx.descriptor.function`` every time a new context is initialized. If for some reason you need to alter the ``ctx.descriptor.function``, you can call :func:`ctx.descriptor.reset_function()` to restore it to its original value. Also consider thread-safety issues when altering global state. What's next? ------------ You can read the :ref:`manual-sqlalchemy` document where the :class:`spyne.model.complex.TTableModel` class and its helpers are introduced. You can also have look at the :ref:`manual-validation` section where Spyne's imperative and declarative input validation features are introduced. .. [#] Here's how that's done: `Magic! `_. :) .. [#] If you just intend to have a convenient way to set additional method metadata, you can assign any value to the ``_udp`` argument of the ``@rpc`` decorator. spyne-spyne-2.14.0/doc/source/manual/05-01_sqlalchemy.rst000066400000000000000000000273621417664205300230520ustar00rootroot00000000000000 .. _manual-sqlalchemy: SQLAlchemy Integration ====================== This tutorial builds on the :ref:`manual-user-manager` tutorial. If you haven't done so, we recommend you to read it first. In this tutorial, we talk about using Spyne tools that make it easy to deal with database-related operations using `SQLAlchemy `_. SQLAlchemy is a well-established and mature SQL generation and ORM library that is well worth the time invested in climbing its learning curve. We will show how to integrate SQLAlchemy and Spyne object definitions, and how to do painless transaction management using Spyne events. There are two ways of integrating with SQLAlchemy: 1. The first and supported method is to use the output of the :class:`spyne.model.complex.TTableModel`. The ``TTableModel`` class is a templated callable that produces a ``ComplexModel`` that has enough information except table name to be mapped with a SQL table. It takes an optional ``metadata`` argument and creates a new one when one isn't supplied. **WARNING:** While the machinery around ``TTableModel`` is in production use in a few places, it should be considered *experimental* as it's a relatively new feature which is not as battle-tested as the rest of the Spyne code. Also, this is only tested with `PostgreSQL `_ and to some extent, `SQLite `_\. We're looking for volunteers to test and integrate other RDBMSs, please open an issue and chime in. 2. The second method is to use :class:`spyne.model.table.TableModel` as a second base class together with the declarative base class (output of the :func:`sqlalchemy.orm.declarative_base` callable). This is deprecated [#]_ and won't be developed any further, yet it also won't be removed in the foreseeable feature as apparently there are people who are quite fine with its quirks and would prefer to have it shipped within the Spyne package. This document will cover only the first method. The documentation for the second method can be found in the :mod:`spyne.model.table` documentation or in the Spyne 2.9 documentation. The semantics of SQLAlchemy's and Spyne's object definition are almost the same, except a few small differences: #. SQLAlchemy's ``Integer`` maps to Spyne's ``Integer32`` or ``Integer64``\, depending on the RDBMS. Spyne's ``Integer``\, as it's an arbitrary-size number, is converted to :class:`sqlalchemy.Decimal` type as it's the only type that can acommodate arbitrary-size numbers. So it's important to use a bounded integer type like ``Integer32`` or ``Integer64``\, especially as primary key. #. SQLAlchemy's ``UnicodeText`` is Spyne's ``Unicode`` with no ``max_len`` restriction. If you need a length-limited ``UnicodeText``, you can use Spyne's ``Unicode`` object as follows: :: class SomeTable(TableModel): __tablename__ = "some_table" # text some_text = Unicode(2048, db_type=sqlalchemy.UnicodeText) # varchar some_varchar = Unicode(2048) # text some_more_text = Unicode Default mapping for text types is ``varchar``\. Note that the limit is only enforced to incoming data, in this case the database type is bounded only by the limits of the database system. #. Spyne does not reflect all restrictions to the database -- some are only enforced to incoming data when validation is enabled. These include range and value restrictions for numbers, and ``min_len`` and ``pattern`` restrictions for Spyne types. Okay, enough with the introductory & disclaimatory stuff, let's get coding :) There's a fully functional example at :download:`examples/user_manager/server_sqlalchemy.py <../../../examples/user_manager/server_sqlalchemy.py>`\. in the source distribution. First, we need a database handle: :: db = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=db) metadata = MetaData(bind=db) Now, we must define our own ``TableModel`` base class. This must be defined for every ``MetaData`` instance. TableModel = TTableModel(metadata) Doing this is also possible: :: TableModel == TTableModel() TableModel.Attributes.sqla_metadata.bind = db ... but the first method is arguably cleaner. We're finally ready to define Spyne types mapped to SQLAlchemy tables. At this point, we have two options: Do everything with the Spyne markers, or re-use existing SQLAlchemy code we might already have. The Spyne Way ------------- Let's consider the following two class definitions: :: class Permission(TableModel): __tablename__ = 'permission' id = UnsignedInteger32(pk=True) application = Unicode(values=('usermgr', 'accountmgr')) operation = Unicode(values=('read', 'modify', 'delete')) class User(TableModel): __tablename__ = 'user' id = UnsignedInteger32(pk=True) user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+', unique=True) full_name = Unicode(64, pattern='\w+( \w+)+') email = Unicode(64, pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[A-Z]{2,4}') last_pos = Point(2, index='gist') permissions = Array(Permission).store_as('table') A couple of points about the above block: A ``TableModel`` subclass won't be mapped to a database table if it's missing both the ``__table__`` and ``__tablename__`` attributes. As we're defining the table in this object, we just pass the ``__tablename__`` attribute -- the ``__table__`` object (which is a :class:`sqlalchemy.schema.Table` instance) will be generated automatically. The definitions of the ``id``\, ``user_name``\, ``full_name`` and ``email`` fields should be self-explanatory. There are other database-specific arguments that can be passed to the column definition, see the :class:`spyne.model.ModelBase` reference for more information. The ``last_pos`` field is a spatial type -- a 2D point, to be exact. PostGIS docs suggest to use 'gin' or 'gist' indexes with spatial fields. Here we chose to use the 'gist' index [#]_. As for the ``permissions`` field, due to the ``store_as('table')`` call, it will be stored using a one-to-many relationship. Spyne automatically generates a foreign key column inside the ``permission`` table with 'user_id' as default value. If we'd let the ``store_as()`` call out: :: permissions = Array(Permission) ... the permissions field would not exist as far as SQLAlchemy is concerned. Calling ``store_as()`` is just a shortcut for calling ``.customize(store_as='table')``\. While the default is what appears to make most sense when defining such relations, it might not always be appropriate. Spyne offers the so-called "compound option object"s to make it easy to configure persistance options. Using the :class:`spyne.model.complex.table` object, we change the ``permissions`` field to be serialized using the many-to-many pattern: :: from spyne.model.complex import table permissions = Array(Permission).store_as(table(multi=True)) In this case, Spyne takes care of creating a relation table with appropriate foreign key columns. We can also alter column names or the relation table name: :: from spyne.model.complex import table permissions = Array(Permission).store_as(table( multi='user_perm_rel', left='u_id', right='perm_id', )) See the :class:`spyne.model.complex.table` reference for more details on configuring object relations. Using SQL Databases as Hybrid Document Stores ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``'table'`` is not the only option for persisting objects to a database. Other options are ``'json'`` and ``'xml'``\. These use the relevant column types to store the object serialized to JSON or XML. Let's modify the previous example to store the ``Permission`` entity in a JSON column. :: class Permission(ComplexModel): application = Unicode(values=('usermgr', 'accountmgr')) operation = Unicode(values=('read', 'modify', 'delete')) class User(TableModel): __tablename__ = 'user' id = UnsignedInteger32(pk=True) user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+') full_name = Unicode(64, pattern='\w+( \w+)+') email = Unicode(64, pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[A-Z]{2,4}') permissions = Array(Permission).store_as('json') Note that nothing has changed in the ``User`` object except the storage parameter for the ``permissions`` field, whereas the ``Permission`` object now inherits from ``ComplexModel`` and does not have (nor need) a primary key. As the ``Array(Permission)`` is now stored in a document-type column inside the table, it's possible to make arbitrary changes to the schema of the ``Permission`` object without worrying about schema migrations -- If the changes are backwards-compatible, everything will work flawlessly. If not, attributes in that are not defined in the latest object definition will just be ignored [#]_. Such changes are never reflected to the schema. In other words, your clients will never know how your objects are persisted just by looking at your schema alone. You can play with the example at `spyne.io `_ to experiment how Spyne's model engine interacts with SQLAlchemy. Integrating with Existing SQLAlchemy objects -------------------------------------------- Let's consider the following fairly ordinary SQLAlchemy object: :: class User(DeclarativeBase): __tablename__ = 'spyne_user' id = Column(sqlalchemy.Integer, primary_key=True) user_name = Column(sqlalchemy.String(256)) first_name = Column(sqlalchemy.String(256)) last_name = Column(sqlalchemy.String(256)) Assigning an existing SQLAlchemy table to the ``__table__`` attribute of the ``TableModel`` ... :: class User(TableModel): __table__ = User.__table__ ... creates the corresponding Spyne object. This conversion works for simple column types, but complex ORM constructs like ``relationship``\ are not converted. If you want to override which columns are exposed, you must set everything manually: :: class User(TableModel): __table__ = User.__table__ id = UnsignedInteger32 user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+') full_name = Unicode(64, pattern='\w+( \w+)+') email = Unicode(64, pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[A-Z]{2,4}') Any field not listed here does not exist as far as Spyne is concerned. This is still one of the weaker spots of SQLAlchemy integration, please chime in with your ideas on how we should handle different cases! What's next? ------------ This tutorial walks you through most of what you need to know to implement complex, real-world services. You can read the :ref:`manual-metadata` section where service metadata management APIs are introduced, but otherwise, you're mostly set. You also refer to the reference of the documentation or the mailing list if you have further questions. .. [#] The reasons for its depreciation are as follows: #. The old way of trying to fuse metaclasses was a nightmare to maintain. #. The new API can handle existing SQLAlchemy objects via the ``__table__`` attribute trick. #. It's not easy to add arbitrary restrictions (like pattern) when using the SQLAlchemy API. .. [#] It's not possible to use an Array of primitives directly for ``'table'`` storage -- create a ComplexModel with a primary key field as a workaround. (or, you guessed it, send a patch!...) .. [#] To make the case with non-backwards-compatible changes work, an implicit versioning support must be added. Assuming that everybody agrees that this is a good idea, adding this feature would be another interesting project. Feedback is welcome! spyne-spyne-2.14.0/doc/source/manual/05-02_validation.rst000066400000000000000000000220241417664205300230310ustar00rootroot00000000000000 .. _manual-validation: Input Validation ================ The input validation features of Spyne are also mostly inherited from the Soap world and follows the behavior of Xml validation operations as closely as possible. Input validation is an essential component of any distributed system exposed to a non-trusted environment. Examples of validation constraints that Spyne can apply are as follows: - A number that must be within a certain range, - A string that must match with a given regular expression. - A string that can only take certain values. Currently, data validation can be handled by two subsystems: Xml schema validation: Such rules are enforced by lxml's schema validation feature. This is of course only useful for Xml-based protocols. "Soft" validation: Spyne itself implements enforcing a subset of the XmlSchema-type constraints in a protocol-independent way. When using this mode, it's also possible to use Spyne's imperative validation hooks. When validating Xml data, the differences between using "lxml" and "soft" validation are as follows: - Soft validation ignores unknown fields, while *lxml* validation rejects them. - Soft validation doesn't care about namespaces, while *lxml* validation rejects unexpected namespaces. ============================== ======== ========= Criteria lxml soft ============================== ======== ========= Unknown fields reject ignore Unknown namespaces reject ignore Supported transport protocols SOAP/XML any ============================== ======== ========= .. NOTE:: The two validation subsystems operate independently, you can use either one, but not both at the same time. The validator is indicated when instantiating the protocol, by passing either ``validator='soft'`` or ``validator='lxml'`` to the constructor. :: #using 'soft' validation with HttpRpc application = Application([NameOfMonthService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc() ) #using lxml validation with Soap application = Application([UserService], tns='spyne.examples.authentication', interface=Wsdl11(), in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) Simple validation at the Xml schema level ----------------------------------------- This applies to all the primitive data types, and is suitable for simple logical conditions. .. NOTE:: Constraints applied at this level are reflected in the XML schema itself, thus a client that retrieves the WSDL of the service will be able to see what the constraints are. Any primitive type ^^^^^^^^^^^^^^^^^^ Certain generic restrictions can be applied to any type. They are listed below, along with their default values - ``default = None`` - default value if the input is ``None``. - ``nillable = True`` - if ``True``, the item can be null when provided. Note that this constraint only applies when the variable is actually provided in the input document and is ignored if it's not. You should set ``min_occurs=1`` if you want to force this variable to be present in incoming documents. - ``min_occurs = 0`` - set this to 1 to make the type mandatory. Can be set to any positive integer. Note that if ``nillable=False``, the validator will still accept ``null`` values. - ``max_occurs = 1`` - can be set to any strictly positive integer. Values greater than 1 will imply an iterable of objects as native Python type. It can be set to ``unbounded`` or ``decimal.Decimal('inf')`` to denote an array with infinitely many elements. .. NOTE:: You should not use float('inf') as its behavior has inconsistencies between platforms and Python versions. See: https://github.com/arskom/spyne/pull/155 These rules can be combined, the example below illustrates how to create a mandatory string: Unicode(min_occurs=1, min_len=1, nillable=False) Numbers ^^^^^^^ Integers and other countable numerical data types (i.e. except Float or Double) can be compared with specific values, using the following keywords: ``ge``, ``gt``, ``le``, ``lt`` (they correspond to >=, >, <=, <) :: Integer(ge=1, le=12) #an integer between 1 and 12, i.e. 1 <= x <= 12 Strings ^^^^^^^ Strings can be validated against a regular expression: :: Unicode(pattern="[0-9]+") #must contain one or more digits Length checks can be enforced as well: :: Unicode(min_len=5, max_len=10) If you want to keep an incoming bytestream as a ``str`` with a known encoding, that's also possible with the String type. You can specify: - Which encoding the strings must be in - How to handle the situations in which a string cannot be decoded properly (to understand how this works, consult `Python's documentation `_) :: String(encoding = 'win-1251') String(unicode_errors = 'strict') #could be 'replace' or 'ignore' These restrictions can be combined: :: String(encoding='win-1251', max_len=20) String(min_len=5, max_len=20, pattern='[a-z]') Possible values ^^^^^^^^^^^^^^^ Sometimes you may want to allow only a finite set of values, or values which can be difficult to describe in terms of an interval. If this is the case, you can explicitly indicate the set: :: Integer(values=[1984, 13, 45, 42]) Unicode(values=[u"alpha", u"bravo", u"charlie"]) # note the 'u' prefix Advanced validation ^^^^^^^^^^^^^^^^^^^ Spyne offers several primitives for this purpose. Please see the :class:`spyne.model.ModelBase` reference for more information. These primitives are: **validate_string** invoked when the variable is extracted from the input XML data. **validate_native** invoked after the string is converted to a specific Python value. Since all data comes in as a byte stream, when you read it you get a ``str`` instance. So the ``validate_string`` hook is your first line of defense against invalid data. After the string validation passes, the data is converted to its native type. You can then do some additional checks. Validation in this stage is handled by the ``validate_native`` hook. A string validation ^^^^^^^^^^^^^^^^^^^ A custom string type that can not contain the colon symbol (``':'``). We'll have to declare our own class as a subclass of ``Unicode``\: :: class SpecialString(Unicode): """Custom string type that prohibits the use of colons""" @staticmethod def validate_string(cls, value): retval = True if value is not None and ":" in value: retval = False return ( Unicode.validate_string(value) and retval ) A native validation example ^^^^^^^^^^^^^^^^^^^^^^^^^^^ A custom numerical type that verifies whether the number is prime. This time both flavours of validation are combined: *validate_string* to see if it is a number, and then ``validate_native`` to see if it is prime. :: from math import sqrt, floor class Prime(UnsignedInteger): """Custom integer type that only accepts primes.""" @staticmethod def validate_native(cls, value): return ( UnsignedInteger.validate_native(value) and \ all(a % i for i in xrange(2, floor(sqrt(a)))) ) .. NOTE:: Constraints applied at this level do **not** modify the XML schema itself. So a client that retrieves the WSDL of the service will not be aware of these restrictions. Keep this in mind and make sure that validation rules that are not visible in the XML schema are documented elsewhere. .. NOTE:: When overriding ``validate_string`` or ``validate_native`` in a custom type class, the validation functions from the parent class are **not invoked**. If you wish to apply those validation functions as well, you must call them explicitly. Summary ^^^^^^^ - Simple checks can be applied at the XML schema level, you can control: - The length of a string, - The pattern with which a string must comply, - A numeric interval, etc. - *Spyne* can apply arbitrary rules for the validation of input data: - *validate_string* is the first applied filter. - *validate_native* is the applied at the second phase. - Override these functions in your derived class to add new validation rules. - The validation functions must return a *boolean* value. - These rules are **not** shown in the XML schema. What's next? ^^^^^^^^^^^^ Now that you've also learned how to tame incoming data, you can have a look at the :ref:`manual-sqlalchemy` document where we explain how to easily integrate with SQLAlchemy by showing how to map Spyne objects to table definitions and rows returned by database queries. You could also have a look at the :ref:`manual-metadata` section where service metadata management apis are introduced. Otherwise, please refer to the rest of the documentation or the mailing list if you have further questions. spyne-spyne-2.14.0/doc/source/manual/05-03_django.rst000066400000000000000000000120221417664205300221370ustar00rootroot00000000000000 .. _manual-django: Django Integration ================== This tutorial shows how to integrate Spyne into your Django project. Imagine that you want to build TODO-list RPC service that exposes its API via SOAP. Here are our Django models from project/todo/models.py: :: """Models for todo app.""" from django.db import models class TodoList(models.Model): """Represents TODO list.""" name = models.CharField(max_length=100, unique=True) @property def entries(self): return self.todoentry_set.all() class TodoEntry(models.Model): """Represents TODO list entry.""" todo_list = models.ForeignKey(TodoList) description = models.TextField() done = models.BooleanField(default=False) Let's define `get_todo_list(list_name)` method for initial implementation of our API. The method should get unique list name as argument and return information about todo list like id, name and entries array. We are going to implement our API in project/todo/todolists.py. Let's define TodoList and TodoEntry types: .. code-block:: python """API for TODO lists.""" from spyne.util.django import DjangoComplexModel from project.todo.models import (TodoList as DjTodoList, TodoEntry as DjTodoEntry) class TodoEntry(DjangoComplexModel): """Todo list type for API.""" class Attributes(DjangoComplexModel.Attributes): django_model = DjTodoEntry class TodoList(DjangoComplexModel): """Todo entry type for API.""" entries = Array(CartItem).customize(nullable=True) class Attributes(DjangoComplexModel.Attributes): django_model = DjTodoList :class:`DjangoComplexModel` creates mapper for us that maps fields of corresponding Django models to fields of todo types. We decided to add extra ``entries`` field so we can pass todo list with all its entries via API. This field is nullable because empty todo list can be represented as null value. The field is populated from ``DjTodoList.entries`` property that returns entries queryset. If you want to customize mapping between Django and Spyne models or you have custom Django fields you can create own mapper and pass it as `django_mapper = my_mapper` in ``Attributes``. See :class:`spyne.util.django.DjangoComplexModel` for details. Now we are going to define our RPC service: :: from spyne.decorator import rpc from spyne.error import ResourceNotFoundError from spyne.model import primitive from spyne.util.django import DjangoService class TodoService(DjangoService): """Todo list RPC service.""" @rpc(primitive.String, _returns=TodoList) def get_todo_list(ctx, list_name): """Get todo list by unique name. :param list_name: string :returns: TodoList :raises: Client.TodoListNotFound fault when todo list with given name is not found """ return DjTodoList.objects.get(name=list_name) You may notice that we defined ``TodoList`` as return value of `get_todo_list` RPC method but in fact ``DjTodoList`` instance is returned. This trick works because our Django models and Spyne types have common attribute interface. Django specific `spyne.util.django` service captures `DjTodoList.DoesNotExist` exception and transforms it to `Client.TodoListNotFound` fault. By default Spyne creates types that are nullable and optional. Let's override defaults and make our API more strict. We are going to define configuration function in project/utils/spyne.py: :: def configure_spyne(): """Set spyne defaults. Use monkey patching here. """ import spyne.model attrs = spyne.model.ModelBase.Attributes attrs.NULLABLE_DEFAULT = False attrs.min_occurs = 1 Now we are all set to register our SOAP RPC API in Django urlconf. Let's edit project/urls.py: :: from project.utils.spyne import configure_spyne configure_spyne() from spyne.application import Application from spyne.protocol.soap import Soap11 from spyne.server.django import DjangoView as RPCView from project.todo.todolists import TodoService api = Application(services=[TodoService], tns='spyne.django.tutorial', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) urlpatterns = patterns( '', url(r'^api/0.1/', RPCView.as_view(application=api), name='api'), ) First we configure spyne defaults. Then we create Spyne application that stores configuration for our setup. Finally we define view `api` bound to specific url. ``DjangoView.as_view`` created for us :class:`spyne.server.django.DjangoServer` instance that will handle rpc requests. Now we can run Django development server and look at WSDL that defines protocol for our web service at `http://localhost:8000/api/0.1/`. Todo service client can do POST requests to the same url. We have done basic steps to build small RPC service and integrated it into Django project. spyne-spyne-2.14.0/doc/source/manual/06_metadata.rst000066400000000000000000000104551417664205300222460ustar00rootroot00000000000000 .. _manual-metadata: Working with RPC Metadata ========================= This section builds on :ref:`manual-user-manager` section. If you haven’t done so, we recommended you to read it first. In most of the real-world scenarios, an RPC request comes with additional baggage like authentication headers, routing history, and similar information. Spyne comes with rich mechanisms that lets you deal with both protocol and transport metadata. At the protocol level, the input and the output of the rpc function itself are kept in ``ctx.in_object`` and ``ctx.out_object`` attributes of the :class:`spyne.MethodContext` whereas the protocol metadata reside in ``ctx.in_header`` and ``ctx.out_header`` attributes. You can set values to the header attributes in the function bodies or events. You just need to consider the order the events are fired, so that you don't overwrite data. If you want to use headers in a function, you must denote it either in the decorator or the :class:`spyne.service.Service` child that you use to expose your functions. A full example using most of the available metadata functionality is available here: https://github.com/plq/spyne/blob/master/examples/authenticate/server_soap.py Protocol Headers ---------------- As said before, the protocol headers are available in ``ctx.in_header`` and ``ctx.out_header`` objects. You should set the ``ctx.out_header`` to the native value of the declared type. Header objects are defined just like any other object: :: class RequestHeader(ComplexModel): user_name = Mandatory.Unicode session_id = Mandatory.Unicode They can be integrated to the rpc definition either by denoting it in the service definition: :: class UserService(Service): __tns__ = 'spyne.examples.authentication' __in_header__ = RequestHeader @rpc(_returns=Preferences) def some_call(ctx): # (...) Or in the decorator: :: @rpc(_in_header=RequestHeader, _returns=Preferences) It's generally a better idea to set the header types in the ``Service`` child as it's likely that all methods will use it. This will avoid cluttering the service definition with header declarations. The header declaration in the decorator will overwrite the one in the service definition. Transport Headers ----------------- There is currently no general transport header api -- transport-specific apis should be used for setting headers. The only transport that supports headers right now is Http, and you can use ``ctx.transport.resp_headers`` which is a dict where keys are field names and values are field values. Both must be ``str`` instances. Exceptions ---------- Here's a sample custom public exception: :: class PublicKeyError(Fault): __type_name__ = 'KeyError' __namespace__ = 'spyne.examples.authentication' def __init__(self, value): super(PublicKeyError, self).__init__( faultcode='Client.KeyError', faultstring='Value %r not found' % value ) Let's modify the python dict to throw our own exception class: :: class SpyneDict(dict): def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: raise PublicKeyError(key) We can now modify the decorator to expose the exception this service can throw: :: preferences_db = SpyneDict() class UserService(Service): __tns__ = 'spyne.examples.authentication' __in_header__ = RequestHeader @rpc(_throws=PublicKeyError, _returns=Preferences) def get_preferences(ctx): retval = preferences_db[ctx.in_header.user_name] return retval While this is not really necessary in the world of the dynamic languages, it'd still be nice to specify the exceptions your service can throw in the interface document. Plus, intefacing with your services will just feel more natural with languages like Java where exceptions are kept on a short leash. What's next? ^^^^^^^^^^^^ With this document, you know most of what Spyne has to offer for application developers. You can refer to the :ref:`manual-t-and-p` section if you want to implement your own transports and protocols. Otherwise, please refer to the rest of the documentation or the mailing list if you have further questions. spyne-spyne-2.14.0/doc/source/manual/07_t_and_p.rst000066400000000000000000000260631417664205300220750ustar00rootroot00000000000000 .. _manual-t-and-p: Implementing Transports and Protocols ===================================== Some preliminary information would be handy before delving into details: How Exactly is User Code Wrapped? --------------------------------- The following is a more detailed discussion of the concepts introduced in the :ref:`manual-highlevel` chapter. So, when a request arrives to a Spyne server, the server transport decides whether this is a simple interface document request or a RPC request. Every transport has its own way of dealing with this. If the incoming request was for the interface document, it's easy: The interface document needs to be generated and returned as a nice chunk of strings to the client. The server transport first calls :func:`spyne.interface._base.InterfaceBase.build_interface_document` which builds and caches the document and later calls the :func:`spyne.interface._base.InterfaceBase.get_interface_document` that returns the cached document. If it was an RPC request, here's what happens: #. The server must set the ``ctx.in_string`` attribute to a sequence of strings. This will contain the incoming byte stream. #. The server calls the :func:`spyne.server._base.ServerBase.get_in_object` function from its parent class, ``ServerBase``. #. The server then calls the ``create_in_document``, ``decompose_incoming_envelope`` and ``deserialize`` functions from the protocol class in the ``in_protocol`` attribute. The first call parses incoming stream to the protocol serializer's internal representation. This is then split to header and body parts by the second call and deserialized to the native python representation by the third call. #. The server then calls ``get_out_object`` which in turn calls the :func:`spyne.application.Application.process_request` function. #. The ``process_request`` function fires relevant events and calls the :func:`spyne.application.Application.call_wrapper` function. This function is overridable by user, but the overriding function must call the one in :class:`spyne.application.Application`. #. The ``call_wrapper`` function in turn calls the :func:`spyne.service.Service.call_wrapper` function, which has has the same requirements. #. The :func:`spyne.service.Service.call_wrapper` finally calls the user function, and the value is returned to ``process_request`` call, which sets the return value to ``ctx.out_object``. #. The server object now calls the ``get_out_string`` function to put the response as an iterable of strings in ``ctx.out_string``. The ``get_out_string`` function in turn calls the ``serialize`` and ``create_out_string`` functions of the protocol class. #. The server pushes the stream from ctx.out_string back to the client. The same logic applies to client transports, in reverse. So if you want to implement a new transport or protocol, you need to subclass the relevant base class and implement the missing methods. A Transport Example: A DB-Backed Fan-Out Queue ---------------------------------------------- Here's the source code in one file: https://github.com/arskom/spyne/blob/master/examples/queue.py The following block of code is SQLAlchemy boilerplate for creating the database and other related machinery. Under normal conditions, you should pass the sqlalchemy url to the Producer and Consumer instances instead of the connection object itself, but here as we deal with an in-memory database, global variable ugliness is just a nicer way to pass database handles. :: db = create_engine('sqlite:///:memory:') TableModel = TTableModel(MetaData(bind=db)) This is the table where queued messages are stored: :: class TaskQueue(TableModel): __tablename__ = 'task_queue' id = Integer32(primary_key=True) data = ByteArray(nullable=False) This is the table where the task id of the last processed task for each worker is stored. Workers are identified by an integer. :: class WorkerStatus(TableModel): __tablename__ = 'worker_status' worker_id = Integer32(pk=True, autoincrement=False) task = TaskQueue.store_as('table') The consumer is a :class:`spyne.server.ServerBase` child that receives requests by polling the database. The transport is for displaying it in the Wsdl. While it's irrelevant here, it's nice to put it in: :: class Consumer(ServerBase): transport = 'http://sqlalchemy.persistent.queue/' We set the incoming values, create a database connection and set it to `self.session`: :: def __init__(self, db, app, consumer_id): ServerBase.__init__(self, app) self.session = sessionmaker(bind=db)() self.id = consumer_id We also query the worker status table and get the id for the first task. If there is no record for own worker id, the server bootstraps its state: :: if self.session.query(WorkerStatus).get(self.id) is None: self.session.add(WorkerStatus( worker_id=self.id, task_id=0)) self.session.commit() This is the main loop for our server: :: def serve_forever(self): while True: We first get the id of the last processed task: :: last = self.session.query(WorkerStatus) \ .with_lockmode("update") \ .filter_by(worker_id=self.id).one() Which is used to get the next tasks to process: :: task_id = 0 if last.task is not None: task_id = last.task.id task_queue = self.session.query(TaskQueue) \ .filter(TaskQueue.id > task_id) \ .order_by(TaskQueue.id) Each task is an rpc request, so we create a :class:`spyne.MethodContext` instance for each task and set transport-specific data to the ``ctx.transport`` object: :: for task in task_queue: ctx = MethodContext(self.app) ctx.in_string = [task.data] ctx.transport.consumer_id = self.id ctx.transport.task_id = task.id This call parses the incoming request: :: self.get_in_object(ctx) In case of an error when parsing the request, the server logs the error and continues to process the next task in queue. The ``get_out_string`` call is smart enough to notice and serialize the error. If this was a normal server, we'd worry about returning the error to the client as well as logging it. :: if ctx.in_error: self.get_out_string(ctx) logging.error(''.join(ctx.out_string)) continue As the request was parsed correctly, the user method can be called to process the task: :: self.get_out_object(ctx) The server should not care whether the error was an expected or unexpected one. So the error is logged and the server continues to process the next task in queue. :: if ctx.out_error: self.get_out_string(ctx) logging.error(''.join(ctx.out_string)) continue If task processing went fine, the server serializes the out object and logs that instead. :: self.get_out_string(ctx) logging.debug(''.join(ctx.out_string)) Finally, the task is marked as processed. :: last.task = task self.session.commit() Once all tasks in queue are consumed, the server waits a pre-defined amount of time before polling the database for new tasks: :: time.sleep(10) This concludes the worker implementation. But how do we put tasks in the task queue? That's the job of the ``Producer`` class that is implemented as a Spyne client. Implementing clients is a two-stage operation. The main transport logic is in the :class:`spyne.client.RemoteProcedureBase` child that is a native Python callable whose function is to serialize the arguments, send it to the server, receive the reply, deserialize it and pass the return value to the python caller. However, in our case, the client does not return anything as calls are processed asyncronously and the return values are ignored. We start with the constructor, where we initialize the SQLAlchemy database connection factory: :: class RemoteProcedure(RemoteProcedureBase): def __init__(self, db, app, name, out_header): RemoteProcedureBase.__init__(self, db, app, name, out_header) self.Session = sessionmaker(bind=db) The implementation of the client is much simpler because we trust that the Spyne code will do The Right Thing. Here, we serialize the arguments: :: def __call__(self, *args, **kwargs): session = self.Session() self.get_out_object(args, kwargs) self.get_out_string() out_string = ''.join(self.ctx.out_string) And put the resulting bytestream to the database: :: session.add(TaskQueue(data=out_string)) session.commit() session.close() Again, here the function does not return anything because this is an asyncronous client. Here's the ``Producer`` class whose sole purpose is to initialize the right callable factory. :: class Producer(ClientBase): def __init__(self, db, app): ClientBase.__init__(self, db, app) self.service = Service(RemoteProcedure, db, app) This is the worker service that will process the tasks. :: class AsyncService(Service): @rpc(UnsignedInteger) def sleep(ctx, integer): print "Sleeping for %d seconds..." % (integer) time.sleep(integer) And this event is here to do some logging. :: def _on_method_call(ctx): print "This is worker id %d, processing task id %d." % ( ctx.transport.consumer_id, ctx.transport.task_id) AsyncService.event_manager.add_listener('method_call', _on_method_call) It's now time to deploy our service. We start by configuring the logger and creating the necessary sql tables: :: if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) metadata.create_all() We then initialize our application: :: application = Application([AsyncService], 'spyne.async', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) And queue some tasks: :: producer = Producer(db, application) for i in range(10): producer.service.sleep(i) And finally start the one worker to consume the queued tasks: :: consumer = Consumer(db, application, 1) consumer.serve_forever() That's about it! You can switch to another database engine that accepts multiple connections and insert tasks from another connection to see the consumer in action. You could also start other workers in other processes to see the pub-sub functionality. What's Next? ^^^^^^^^^^^^ Start hacking! Good luck, and be sure to pop out to the mailing list if you have questions. spyne-spyne-2.14.0/doc/source/manual/index.rst000066400000000000000000000006361417664205300212700ustar00rootroot00000000000000 .. _manual: Spyne Manual ============= The API reference is not the best resource when learning use a new software library. Here, you will find a collection of documents that show you various aspects of Spyne by examples. .. toctree:: :maxdepth: 2 01_highlevel 02_helloworld 03_types 04_usermanager 05-01_sqlalchemy 05-02_validation 05-03_django 06_metadata 07_t_and_p spyne-spyne-2.14.0/doc/source/manual/relational.rst000066400000000000000000000005201417664205300223030ustar00rootroot00000000000000column_property example: ``` Invoice.append_field('cost', Decimal( exc_db=True, str_format="${:,.2f}", read_only=True, mapper_property=column_property(sql .select([sql.func.sum(InvoiceItem.cost)]) .where( D91InvoiceItem.invoice_id == Invoice.id, ) .as_scalar() ), )) ``` spyne-spyne-2.14.0/doc/source/migration.rst000066400000000000000000000103671417664205300206770ustar00rootroot00000000000000 .. _migration: *************** Migration Guide *************** .. _migration-210-211: 2.10 => 2.11 ============ While Spyne tries very hard not to break backwards compatibility across minor releases, fixes to some blatant bugs that we just can't stand having around anymore that ship with 2.11 do have the possibility of breaking existing code. The good news about this is that, in most of the cases, they find inconsistencies in your code and force you to fix them before they hurt you one way or the other. So here's a list of things that you should look for in case your daemon refuses to boot after switching to Spyne 2.11: 1) **Schema non-determinism due to inheritance**: Spyne now adds all child classes of a given parent class to the Xml Schema document, regardless of whether it's used in service definitions or not. This is a first step towards implementing polymorphic responses. So when a subclass contains a field that is also present in the parent class, you will see a "content model not determinist" [sic] error and the daemon will refuse to boot. This error could be hidden in cases where the subclass was not explicitly used as a type marker in @rpc either directly or indirectly. **Fix**: Make sure that the offending field is present in only one of the parent or child classes. Please note that common fields in sibling classes are not supposed to cause any issues. 2) **Unequal number of parameters in @rpc and function definition**: Spyne 2.10 did not care when @rpc had more arguments than the actual function definition. Spyne 2.11 won't tolerate this and the daemon will refuse to boot. **Fix**: Make sure the number of arguments to @rpc and the function it decorates are consistent. 3) **Declared field order can change**: The field order inside the ```` tags in Xml Schema (and naturally Wsdl) documents should *in theory* stay the same, but we never know as CPython offers no guarantees about the element order consistency in its hashmap implementation. **Fix**: Explicitly declare ``_type_info`` as a sequence of ``(field_name, field_type)`` pairs. This is not much of a problem in Python 3, as it's possible to customize the class dict passed to the metaclass thanks to ``__prepare__``. We now pass an ordered dict by default in Python 3 so that the field order is the same as the field declaration order in class definition. However, some folks wanted the same functionality in Python 2 so bad that they dared to submit this horrendous hack: https://github.com/arskom/spyne/pull/343 You can use it to make sure field order stays consistent across Spyne releases and CPython implementations. As it seemed to work OK with CPython 2.6 and 2.7 and PyPy, we decided to ship it with 2.11 after making sure that it's strictly opt-in. Please test it with your environment and report back as it's relying, as far as we can tell, on some implementation details of CPython. 4) **Change in class declaration order**: Spyne 2.11 uses a proper topological sorting algorithm for determining the order of the object definitions in the Xml Schema document. So the order of these will certainly be different from what 2.10 generates. This is not supposed to cause any issues though. Fingers crossed! **Fix:** There is no fix short of reverting the toposort commits. 5) **Possible change in automatically generated type names:** Partly as a result of the above point, but also as a result of more robust type enumeration logic, auto-generated type names could be different from what 2.10 generates, which may break break SOAP clients that use statically compiled copies of the WSDL document. **Fix:** Explicitly set type names of the markers you customize using the ``type_name`` argument. 6) **String or Unicode types may fail to (de)serialize:** As we removed hard-coded utf8 defaults from everywhere, code that silently worked before now can fail with ``"You need to define a source encoding for decoding incoming unicode values``. **Fix:** Just add ``'encoding='utf8'`` to the relevant types. Please don't hesitate to contact us via people@spyne.io if you think you have stumbled upon a backwards compatibility issue that wasn't elaborated above. spyne-spyne-2.14.0/doc/source/reference/000077500000000000000000000000001417664205300201035ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/reference/application.rst000066400000000000000000000003211417664205300231340ustar00rootroot00000000000000 Application Definition ====================== .. autoclass:: spyne.application.Application :members: :inherited-members: .. autofunction:: spyne.application.return_traceback_in_unhandled_exceptions spyne-spyne-2.14.0/doc/source/reference/auxproc.rst000066400000000000000000000007641417664205300223250ustar00rootroot00000000000000 .. _reference-auxproc-base: Auxiliary Processors ==================== .. automodule:: spyne.auxproc AuxProcBase ----------- .. autoclass:: spyne.auxproc._base.AuxProcBase :members: :inherited-members: .. _reference-auxproc-sync: SyncAuxProc ----------- .. autoclass:: spyne.auxproc.sync.SyncAuxProc :members: :show-inheritance: .. _reference-auxproc-thread: ThreadAuxProc ------------- .. autoclass:: spyne.auxproc.thread.ThreadAuxProc :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/base.rst000066400000000000000000000006151417664205300215510ustar00rootroot00000000000000 .. _reference-base: Fundamental Data Structures =========================== MethodContext ------------- .. autoclass:: spyne.MethodContext :members: :show-inheritance: MethodDescriptor ---------------- .. autoclass:: spyne.MethodDescriptor :members: :show-inheritance: .. _reference-eventmanager: EventManager ------------ .. autoclass:: spyne.EventManager :members: spyne-spyne-2.14.0/doc/source/reference/client.rst000066400000000000000000000005551417664205300221200ustar00rootroot00000000000000 Client Transports ================= Client Base Class -------------------- .. automodule:: spyne.client._base :members: :inherited-members: HTTP ---- .. automodule:: spyne.client.http :members: :inherited-members: :undoc-members: ZeroMQ ------ .. automodule:: spyne.client.zeromq :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/decorator.rst000066400000000000000000000001461417664205300226200ustar00rootroot00000000000000 RPC Decorators ============== .. automodule:: spyne.decorator :members: :inherited-members: spyne-spyne-2.14.0/doc/source/reference/error.rst000066400000000000000000000001471417664205300217700ustar00rootroot00000000000000 Common Exceptions ================= .. automodule:: spyne.error :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/index.rst000066400000000000000000000004261417664205300217460ustar00rootroot00000000000000 .. _reference-index: ==================== Spyne API Reference ==================== .. toctree:: :maxdepth: 3 base application service decorator error model/index interface protocol/index client server/index auxproc util spyne-spyne-2.14.0/doc/source/reference/interface.rst000066400000000000000000000011361417664205300225760ustar00rootroot00000000000000 Interfaces ========== The `spyne.interface` package contains the implementations of various interface document standards. Interface Class -------------------- .. autoclass:: spyne.interface.Interface :members: :show-inheritance: Interface Document Base Class ----------------------------- .. autoclass:: spyne.interface.InterfaceDocumentBase :members: :show-inheritance: XML Schema ---------- .. automodule:: spyne.interface.xml_schema._base :members: :inherited-members: Wsdl 1.1 -------- .. automodule:: spyne.interface.wsdl.wsdl11 :members: :inherited-members: spyne-spyne-2.14.0/doc/source/reference/model/000077500000000000000000000000001417664205300212035ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/reference/model/binary.rst000066400000000000000000000003671417664205300232270ustar00rootroot00000000000000 .. _reference-model-binary: Binary Types ------------ .. automodule:: spyne.model.binary .. autoclass:: spyne.model.binary.ByteArray :members: :show-inheritance: .. autoclass:: spyne.model.binary.File :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/model/complex.rst000066400000000000000000000004531417664205300234060ustar00rootroot00000000000000 .. _reference-model-complex: Complex ------- .. automodule:: spyne.model.complex :members: :show-inheritance: .. function:: TTableModel(metadata=None) A TableModel template that generates a new TableModel class for each call. If metadata is not supplied, a new one is instantiated. spyne-spyne-2.14.0/doc/source/reference/model/enum.rst000066400000000000000000000001201417664205300226720ustar00rootroot00000000000000 .. _reference-model-enum: Enum ---- .. autofunction:: spyne.model.enum.Enum spyne-spyne-2.14.0/doc/source/reference/model/fault.rst000066400000000000000000000001651417664205300230520ustar00rootroot00000000000000 .. _reference-model-fault: Fault ----- .. autoclass:: spyne.model.fault.Fault :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/model/index.rst000066400000000000000000000017441417664205300230520ustar00rootroot00000000000000 .. _reference-model: Models ====== The `spyne.model` package contains the Spyne type markers to denote primitive and complex types in object and method definitions. Spyne has built-in support for most common data types and provides an API for those who'd like to implement their own. There are five types of models in Spyne: .. toctree:: :maxdepth: 2 primitive binary enum complex fault sql Base Classes ------------ .. autoclass:: spyne.model.ModelBase :members: :special-members: :exclude-members: __dict__,__weakref__ .. autoattribute:: spyne.model.ModelBase.__orig__ .. autoattribute:: spyne.model.ModelBase.__extends__ .. autoattribute:: spyne.model.ModelBase.__namespace__ .. autoattribute:: spyne.model.ModelBase.__type_name__ .. autoclass:: spyne.model.SimpleModel :members: :show-inheritance: :special-members: :exclude-members: __dict__,__weakref__ Modifiers --------- .. autofunction:: spyne.model.Mandatory spyne-spyne-2.14.0/doc/source/reference/model/primitive.rst000066400000000000000000000002021417664205300237370ustar00rootroot00000000000000 .. _reference-model-primitive: Primitives ---------- .. automodule:: spyne.model.primitive :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/model/sql.rst000066400000000000000000000002171417664205300225340ustar00rootroot00000000000000 .. _reference-model-persistance: Persisting Objects ------------------ .. automodule:: spyne.model.table :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/protocol/000077500000000000000000000000001417664205300217445ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/reference/protocol/base.rst000066400000000000000000000002241417664205300234060ustar00rootroot00000000000000 .. _reference-protocol-base: Protocol Base Class -------------------- .. automodule:: spyne.protocol._base :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/protocol/dictdoc.rst000066400000000000000000000002271417664205300241100ustar00rootroot00000000000000 .. _reference-protocol-dictdoc: Dictionary Document ------------------- .. automodule:: spyne.protocol.dictdoc :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/protocol/http.rst000066400000000000000000000002071417664205300234540ustar00rootroot00000000000000 .. _reference-protocol-http: Http ---- .. automodule:: spyne.protocol.http :members: :show-inheritance: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/protocol/index.rst000066400000000000000000000003711417664205300236060ustar00rootroot00000000000000 .. _reference-protocol: Protocols ========= The `spyne.protocol` package contains the implementations of various remote procedure call protocols. .. toctree:: :maxdepth: 2 base http xml soap dictdoc json msgpack spyne-spyne-2.14.0/doc/source/reference/protocol/json.rst000066400000000000000000000001631417664205300234470ustar00rootroot00000000000000 .. _reference-protocol-json: Json ---- .. automodule:: spyne.protocol.json :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/protocol/msgpack.rst000066400000000000000000000002101417664205300241140ustar00rootroot00000000000000 .. _reference-protocol-msgpack: MessagePack ----------- .. automodule:: spyne.protocol.msgpack :members: :show-inheritance: spyne-spyne-2.14.0/doc/source/reference/protocol/soap.rst000066400000000000000000000002271417664205300234410ustar00rootroot00000000000000 .. _reference-protocol-soap: Soap 1.1 -------- .. automodule:: spyne.protocol.soap.soap11 :members: :show-inheritance: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/protocol/xml.rst000066400000000000000000000002031417664205300232710ustar00rootroot00000000000000 .. _reference-protocol-xml: Xml --- .. automodule:: spyne.protocol.xml :members: :show-inheritance: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/000077500000000000000000000000001417664205300214115ustar00rootroot00000000000000spyne-spyne-2.14.0/doc/source/reference/server/django.rst000066400000000000000000000002631417664205300234060ustar00rootroot00000000000000 .. _reference-server-django: .. _spyne.server.django: Http (Django) ------------- .. automodule:: spyne.server.django :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/index.rst000066400000000000000000000003651417664205300232560ustar00rootroot00000000000000 Server Transports ================= .. toctree:: :maxdepth: 2 wsgi twisted django pyramid zeromq null Server Base Class ----------------- .. automodule:: spyne.server._base :members: :inherited-members: spyne-spyne-2.14.0/doc/source/reference/server/null.rst000066400000000000000000000002201417664205300231070ustar00rootroot00000000000000 .. _reference-server-null: NullServer ---------- .. automodule:: spyne.server.null :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/pyramid.rst000066400000000000000000000002361417664205300236110ustar00rootroot00000000000000 .. _reference-server-pyramid: Http (Pyramid) -------------- .. automodule:: spyne.server.pyramid :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/twisted.rst000066400000000000000000000004161417664205300236270ustar00rootroot00000000000000 .. _reference-server-twisted: Http (Twisted) -------------- .. automodule:: spyne.server.twisted.http :members: :inherited-members: :undoc-members: .. automodule:: spyne.server.twisted.websocket :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/wsgi.rst000066400000000000000000000002221417664205300231100ustar00rootroot00000000000000 .. _reference-server-wsgi: Http (WSGI) ----------- .. automodule:: spyne.server.wsgi :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/server/zeromq.rst000066400000000000000000000002141417664205300234550ustar00rootroot00000000000000 .. _reference-server-zeromq: ZeroMQ ------ .. automodule:: spyne.server.zeromq :members: :inherited-members: :undoc-members: spyne-spyne-2.14.0/doc/source/reference/service.rst000066400000000000000000000001551417664205300222760ustar00rootroot00000000000000 Service Definition ================== .. automodule:: spyne.service :members: :inherited-members: spyne-spyne-2.14.0/doc/source/reference/util.rst000066400000000000000000000014031417664205300216100ustar00rootroot00000000000000 Miscellanous Spyne Utilities ============================= Class Dictionary ---------------- .. automodule:: spyne.util.cdict :members: .. _spyne.util.django: Django Integration Utils ------------------------ .. automodule:: spyne.util.django :members: Element Tree Conversion ----------------------- .. automodule:: spyne.util.etreeconv :members: Simple Wrappers --------------- .. automodule:: spyne.util.simple :members: Xml Utilities ------------- .. automodule:: spyne.util.xml :members: Ordered Dictionary ------------------ .. automodule:: spyne.util.odict :members: Ordered Set ----------- .. automodule:: spyne.util.oset :members: Protocol Helpers ---------------- .. automodule:: spyne.util.protocol :members: spyne-spyne-2.14.0/doc/source/tests.rst000066400000000000000000000000521417664205300200360ustar00rootroot00000000000000 .. include:: ../../spyne/test/README.rst spyne-spyne-2.14.0/examples/000077500000000000000000000000001417664205300157165ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/arrays_simple_vs_complex.py000077500000000000000000000056631417664205300234160ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ For testing different kind of arrays with different protocols. """ import logging from spyne import Application, rpc, Service, Unicode, ComplexModel, Array from spyne.protocol.xml import XmlDocument from spyne.protocol.json import JsonDocument from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication class Permission(ComplexModel): __namespace__ = 'some_ns' application = Unicode feature = Unicode v = [ Permission(application='app', feature='f1'), Permission(application='app', feature='f2'), ] class HelloWorldService(Service): @rpc(_returns=Array(Permission)) def simple(ctx): return v @rpc(_returns=Permission.customize(max_occurs=float('inf'))) def complex(ctx): return v if __name__=='__main__': from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) application = Application([HelloWorldService], 'spyne.examples.hello.http', in_protocol=HttpRpc(validator='soft'), out_protocol=XmlDocument(), ) wsgi_application = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_application) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/async_.py000077500000000000000000000070451417664205300175550ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # # FIXME: This example is not working. It's here just so we don't forget about # it. Please ignore this. # """ This is a very simple async service that sleeps for a specified number of seconds and then call back the caller with a message. This kicks off a new Thread for each request, which is not recommended for a real-world application. Spyne does not provide any thread management or scheduling mechanism, the service is responsible for the execution of the async process. """ import logging import time from threading import Thread from spyne.application import Application from spyne.decorator import rpc from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.service import Service from spyne.util import get_callback_info class SleepingService(Service): @rpc(Integer, _is_async=True) def sleep(ctx, seconds): msgid, replyto = get_callback_info() def run(): time.sleep(seconds) client = make_service_client(replyto, self) client.woke_up('good morning', msgid=msgid) Thread(target=run).start() @rpc(String, _is_callback=True) def woke_up(ctx, message): pass if __name__=='__main__': logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) try: from wsgiref.simple_server import make_server except ImportError: logging.error("Error: example server code requires Python >= 2.5") application = Application([SleepingService], 'spyne.examples.async', in_protocol=Soap11(), out_protocol=Soap11()) server = make_server('127.0.0.1', 8000, WsgiApplication(application)) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/authentication/000077500000000000000000000000001417664205300207355ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/authentication/client_suds.py000077500000000000000000000045631417664205300236360ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from suds import WebFault from suds.client import Client c = Client('http://localhost:8000/app/?wsdl') user_name = 'neo' session_id = c.service.authenticate(user_name, 'Wh1teR@bbit') print ('Authentication for %r successful. Session id: %r' % (user_name, session_id)) request_header = c.factory.create('RequestHeader') request_header.session_id = session_id request_header.user_name = user_name c.set_options(soapheaders=request_header) print('Preferences for %r:' % user_name) print(c.service.get_preferences(user_name)) try: print(c.service.get_preferences('trinity')) except WebFault, e: print(e) try: print(c.service.get_preferences('smith')) except WebFault, e: print(e) spyne-spyne-2.14.0/examples/authentication/http_cookie/000077500000000000000000000000001417664205300232455ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/authentication/http_cookie/client_suds.py000077500000000000000000000040441417664205300261400ustar00rootroot00000000000000#!/usr/bin/env python2 #encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # TODO: python3 port from suds import WebFault from suds.client import Client c = Client('http://localhost:8000/app/?wsdl') user_name = 'neo' c.service.authenticate(user_name, 'Wh1teR@bbit') print ('Authentication for %r successful.' % user_name) print('Preferences for %r:' % user_name) print(c.service.get_preferences(user_name)) try: print(c.service.get_preferences('smith')) except WebFault, e: print(e) spyne-spyne-2.14.0/examples/authentication/http_cookie/server_soap.py000077500000000000000000000164371417664205300261650ustar00rootroot00000000000000#!/usr/bin/env python #encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging import random import sys import base64 from pprint import pformat from spyne.util.six.moves.http_cookies import SimpleCookie # bcrypt seems to be among the latest consensus around cryptograpic circles on # storing passwords. # You need the package from http://code.google.com/p/py-bcrypt/ # You can install it by running easy_install py-bcrypt. try: import bcrypt except ImportError: print('easy_install --user py-bcrypt to get it.') raise from spyne import Unicode, Application, rpc, Service from spyne import M, ComplexModel, Fault, String from spyne import ResourceNotFoundError from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication class PublicKeyError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self, value): super(PublicKeyError, self).__init__( faultcode='Client.KeyError', faultstring='Value %r not found' % value ) class AuthenticationError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self, user_name): # TODO: self.transport.http.resp_code = HTTP_401 super(AuthenticationError, self).__init__( faultcode='Client.AuthenticationError', faultstring='Invalid authentication request for %r' % user_name ) class AuthorizationError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self): # TODO: self.transport.http.resp_code = HTTP_401 super(AuthorizationError, self).__init__( faultcode='Client.AuthorizationError', faultstring='You are not authorized to access this resource.' ) class UnauthenticatedError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self): super(UnauthenticatedError, self).__init__( faultcode='Client.UnauthenticatedError', faultstring='This resource can only be accessed after authentication.' ) class SpyneDict(dict): def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: raise PublicKeyError(key) class Preferences(ComplexModel): __namespace__ = 'spyne.examples.authentication' language = String(max_len=2) time_zone = String user_db = { 'neo': bcrypt.hashpw(b'Wh1teR@bbit', bcrypt.gensalt()), } session_db = set() preferences_db = SpyneDict({ 'neo': Preferences(language='en', time_zone='Underground/Zion'), 'smith': Preferences(language='xx', time_zone='Matrix/Core'), }) class Encoding: SESSION_ID = 'ascii' USER_NAME = PASSWORD = CREDENTIALS = 'utf8' class UserService(Service): __tns__ = 'spyne.examples.authentication' @rpc(M(Unicode), M(Unicode), _throws=AuthenticationError) def authenticate(ctx, user_name, password): ENC_C = Encoding.CREDENTIALS ENC_SID = Encoding.SESSION_ID password_hash = user_db.get(user_name, None) if password_hash is None: raise AuthenticationError(user_name) password_b = password.encode(ENC_C) if bcrypt.hashpw(password_b, password_hash) != password_hash: raise AuthenticationError(user_name) session_id = '%x' % (random.randint(1<<128, (1<<132)-1)) session_key = ( user_name.encode(ENC_C), session_id.encode(ENC_SID), ) session_db.add(session_key) cookie = SimpleCookie() cookie["session-id"] = \ base64.urlsafe_b64encode(b"\0".join(session_key)) \ .decode('ascii') # find out how to do urlsafe_b64encodestring cookie["session-id"]["max-age"] = 3600 header_name, header_value = cookie.output().split(":", 1) ctx.transport.resp_headers[header_name] = header_value.strip() logging.debug("Response headers: %s", pformat(ctx.transport.resp_headers)) @rpc(M(String), _throws=PublicKeyError, _returns=Preferences) def get_preferences(ctx, user_name): # Only allow access to the users own preferences. if user_name != ctx.udc: raise AuthorizationError() retval = preferences_db[user_name] return retval def _on_method_call(ctx): if ctx.descriptor.name == "authenticate": # No checking of session cookie for call to authenticate return logging.debug("Request headers: %s", pformat(ctx.transport.req_env)) cookie = SimpleCookie() http_cookie = ctx.transport.req_env.get("HTTP_COOKIE") if http_cookie: cookie.load(http_cookie) if "session-id" not in cookie: raise UnauthenticatedError() session_cookie = cookie["session-id"].value user_name, session_id = base64.urlsafe_b64decode(session_cookie) \ .split(b"\0", 1) session_id = tuple(base64.urlsafe_b64decode(session_cookie).split(b"\0", 1)) if not session_id in session_db: raise AuthenticationError(session_id[0]) ctx.udc = session_id[0].decode(Encoding.USER_NAME) UserService.event_manager.add_listener('method_call', _on_method_call) if __name__=='__main__': from spyne.util.wsgi_wrapper import run_twisted logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('twisted').setLevel(logging.DEBUG) application = Application([UserService], tns='spyne.examples.authentication', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) wsgi_app = WsgiApplication(application) wsgi_app.doc.wsdl11.xsl_href = "wsdl-viewer.xsl" twisted_apps = [ (wsgi_app, b'app'), ] sys.exit(run_twisted(twisted_apps, 8000)) spyne-spyne-2.14.0/examples/authentication/server_soap.py000077500000000000000000000137651417664205300236560ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging import random import sys # bcrypt seems to be among the latest consensus around cryptograpic circles on # storing passwords. # You need the package from http://code.google.com/p/py-bcrypt/ # You can install it by running easy_install py-bcrypt. try: import bcrypt except ImportError: print('easy_install --user py-bcrypt to get it.') raise from spyne.application import Application from spyne.decorator import rpc from spyne.error import ArgumentError from spyne.model.complex import ComplexModel from spyne.model.fault import Fault from spyne.model.primitive import Mandatory from spyne.model.primitive import String from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.service import Service class PublicKeyError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self, value): super(PublicKeyError, self).__init__( faultstring='Value %r not found' % value) class AuthenticationError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self, user_name): # TODO: self.transport.http.resp_code = HTTP_401 super(AuthenticationError, self).__init__( faultcode='Client.AuthenticationError', faultstring='Invalid authentication request for %r' % user_name) class AuthorizationError(Fault): __namespace__ = 'spyne.examples.authentication' def __init__(self): # TODO: self.transport.http.resp_code = HTTP_401 super(AuthorizationError, self).__init__( faultcode='Client.AuthorizationError', faultstring='You are not authozied to access this resource.') class SpyneDict(dict): def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: raise PublicKeyError(key) class RequestHeader(ComplexModel): __namespace__ = 'spyne.examples.authentication' session_id = Mandatory.String user_name = Mandatory.String class Preferences(ComplexModel): __namespace__ = 'spyne.examples.authentication' language = String(max_len=2) time_zone = String user_db = { 'neo': bcrypt.hashpw('Wh1teR@bbit', bcrypt.gensalt()), } session_db = set() preferences_db = SpyneDict({ 'neo': Preferences(language='en', time_zone='Underground/Zion'), 'smith': Preferences(language='xx', time_zone='Matrix/Core'), }) class AuthenticationService(Service): __tns__ = 'spyne.examples.authentication' @rpc(Mandatory.String, Mandatory.String, _returns=String, _throws=AuthenticationError) def authenticate(ctx, user_name, password): password_hash = user_db.get(user_name, None) if password_hash is None: raise AuthenticationError(user_name) if bcrypt.hashpw(password, password_hash) == password_hash: session_id = (user_name, '%x' % random.randint(1 << 124, (1 << 128) - 1)) session_db.add(session_id) else: raise AuthenticationError(user_name) return session_id[1] class UserService(Service): __tns__ = 'spyne.examples.authentication' __in_header__ = RequestHeader @rpc(Mandatory.String, _throws=PublicKeyError, _returns=Preferences) def get_preferences(ctx, user_name): if user_name == 'smith': raise AuthorizationError() retval = preferences_db[user_name] return retval def _on_method_call(ctx): if ctx.in_object is None: raise ArgumentError("RequestHeader is null") if not (ctx.in_header.user_name, ctx.in_header.session_id) in session_db: raise AuthenticationError(ctx.in_object.user_name) UserService.event_manager.add_listener('method_call', _on_method_call) if __name__ == '__main__': from spyne.util.wsgi_wrapper import run_twisted logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('twisted').setLevel(logging.DEBUG) application = Application([AuthenticationService, UserService], tns='spyne.examples.authentication', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) twisted_apps = [ (WsgiApplication(application), 'app'), ] sys.exit(run_twisted(twisted_apps, 8000)) spyne-spyne-2.14.0/examples/auxproc.py000077500000000000000000000061531417664205300177610ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import sys import time import logging from wsgiref.simple_server import make_server from spyne import Application, rpc, Integer, Service from spyne.protocol.http import HttpRpc from spyne.protocol.xml import XmlDocument from spyne.server.wsgi import WsgiApplication # Requires Python >=2.7 from spyne.auxproc.thread import ThreadAuxProc from spyne.auxproc.sync import SyncAuxProc host = '127.0.0.1' port = 9753 class SomeService(Service): @rpc(Integer) def block(ctx, seconds): """Blocks the reactor for given number of seconds.""" logging.info("Primary sleeping for %d seconds." % seconds) time.sleep(seconds) class SomeAuxService(Service): __aux__ = ThreadAuxProc() # change this to SyncAuxProc to see the difference @rpc(Integer) def block(ctx, seconds): """Blocks the reactor for given number of seconds.""" logging.info("Auxiliary sleeping for %d seconds." % (seconds * 2)) time.sleep(seconds * 2) def main(): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) services = (SomeService, SomeAuxService) application = Application(services, 'spyne.examples.auxproc', in_protocol=HttpRpc(), out_protocol=XmlDocument()) server = make_server(host, port, WsgiApplication(application)) logging.info("listening to http://%s:%d" % (host, port)) return server.serve_forever() if __name__ == '__main__': sys.exit(main()) spyne-spyne-2.14.0/examples/cherry/000077500000000000000000000000001417664205300172125ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/cherry/wsgi.py000077500000000000000000000076371417664205300205550ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a Http api using Spyne. Here's a sample: $ curl http://localhost:8000/say_hello?name=Dave\×=3 ["Hello, Dave", "Hello, Dave", "Hello, Dave"] """ import sys import logging from spyne import Application, rpc, Service, Iterable, UnsignedInteger, \ String from spyne.protocol.json import JsonDocument from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication from spyne.util.cherry import cherry_graft_and_start class HelloWorldService(Service): @rpc(String, UnsignedInteger, _returns=Iterable(String)) def say_hello(ctx, name, times): """ Docstrings for service methods do appear as documentation in the interface documents. What fun! :param name: The name to say hello to :param times: The number of times to say hello :returns: An array of 'Hello, ' strings, repeated times. """ for i in range(times): yield 'Hello, %s' % name if __name__ == '__main__': # Python daemon boilerplate logging.basicConfig(level=logging.DEBUG) # Instantiate the application by giving it: # * The list of services it should wrap, # * A namespace string. # * An input protocol. # * An output protocol. application = Application([HelloWorldService], 'spyne.examples.hello.http', # The input protocol is set as HttpRpc to make our service easy to # call. Input validation via the 'soft' engine is enabled. (which is # actually the the only validation method for HttpRpc.) in_protocol=HttpRpc(validator='soft'), # The ignore_wrappers parameter to JsonDocument simplifies the reponse # dict by skipping outer response structures that are redundant when # the client knows what object to expect. out_protocol=JsonDocument(ignore_wrappers=True), ) # Now that we have our application, we must wrap it inside a transport. # In this case, we use Spyne's standard Wsgi wrapper. Spyne supports # popular Http wrappers like Twisted, Django, Pyramid, etc. as well as # a ZeroMQ (REQ/REP) wrapper. wsgi_application = WsgiApplication(application) sys.exit(cherry_graft_and_start(wsgi_application)) spyne-spyne-2.14.0/examples/cloth/000077500000000000000000000000001417664205300170275ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/cloth/push.py000077500000000000000000000056451417664205300203750ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a Http api using Spyne's push utilities. """ import logging from spyne.application import Application from spyne.decorator import rpc from spyne.protocol.cloth import XmlCloth from spyne.protocol.http import HttpRpc from spyne.service import Service from spyne.model.complex import Iterable from spyne.model.primitive import UnsignedInteger from spyne.model.primitive import String from spyne.server.wsgi import WsgiApplication class HelloWorldService(Service): @rpc(String, UnsignedInteger, _returns=Iterable(String)) def say_hello(ctx, name, times): def cb(ret): for i in range(times): ret.append('Hello, %s' % name) return Iterable.Push(cb) if __name__=='__main__': from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) application = Application([HelloWorldService], 'spyne.examples.hello.http', in_protocol=HttpRpc(validator='soft'), out_protocol=XmlCloth(), ) wsgi_application = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_application) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/cloth/simple.py000077500000000000000000000064071417664205300207040ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a Http api using Spyne's push utilities. """ import logging from spyne.application import Application from spyne.decorator import rpc from spyne.protocol.html import HtmlColumnTable from spyne.protocol.http import HttpRpc from spyne.service import Service from spyne.model.complex import Iterable from spyne.model.primitive import UnsignedInteger from spyne.model.primitive import String from spyne.server.wsgi import WsgiApplication class HelloWorldService(Service): @rpc(String, UnsignedInteger, _returns=Iterable(String)) def say_hello(ctx, name, times): def cb(ret): for i in range(times): ret.append('Hello, %s' % name) return Iterable.Push(cb) if __name__=='__main__': from lxml.builder import E logging.basicConfig(level=logging.DEBUG) cloth = E.html(E.body( E.style(""" td,th { border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 1px solid; margin: 0; }""", type="text/css" ), spyne="", )) application = Application([HelloWorldService], 'spyne.examples.hello.http', in_protocol=HttpRpc(validator='soft'), out_protocol=HtmlColumnTable(cloth=cloth), ) wsgi_application = WsgiApplication(application) from wsgiref.simple_server import make_server server = make_server('127.0.0.1', 8000, wsgi_application) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/complex.py000077500000000000000000000131271417664205300177460ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This example shows how to define and use complex structures in spyne. This example uses an extremely simple in-memory dictionary to store the User objects. """ import logging import random from spyne import Application, rpc, Array, ComplexModel, Integer, String, \ Service, ResourceNotFoundError from spyne.protocol.http import HttpRpc from spyne.protocol.xml import XmlDocument from spyne.server.wsgi import WsgiApplication user_database = {} userid_seq = 1 chars = [chr(i) for i in range(ord('a'), ord('z'))] def randchars(n): return ''.join(random.choice(chars) for _ in range(n)) class Permission(ComplexModel): __namespace__ = "permission" app = String(values=['library', 'delivery', 'accounting']) perms = String(min_occurs=1, max_occurs=2, values=['read', 'write']) class User(ComplexModel): __namespace__ = "user" userid = Integer username = String firstname = String lastname = String permissions = Array(Permission) # add superuser to the 'database' all_permissions = ( Permission(app='library', perms=['read', 'write']), Permission(app='delivery', perms=['read', 'write']), Permission(app='accounting', perms=['read', 'write']), ) def randperms(n): for p in random.sample(all_permissions, n): yield Permission(app=p.app, perms=random.sample(p.perms, random.randint(1, 2))) user_database[0] = User( userid=0, username='root', firstname='Super', lastname='User', permissions=all_permissions ) def add_user(user): global user_database global userid_seq user.userid = userid_seq userid_seq = userid_seq + 1 user_database[user.userid] = user class UserManager(Service): @rpc(User, _returns=Integer) def add_user(ctx, user): add_user(user) return user.userid @rpc(_returns=User) def super_user(ctx): return user_database[0] @rpc(_returns=User) def random_user(ctx): retval = User( username=randchars(random.randrange(3, 12)), firstname=randchars(random.randrange(3, 12)).title(), lastname=randchars(random.randrange(3, 12)).title(), permissions=randperms(random.randint(1, len(all_permissions))) ) add_user(retval) return retval @rpc(Integer, _returns=User) def get_user(ctx, userid): global user_database # If you rely on dict lookup raising KeyError here, you'll return an # internal error to the client, which tells the client that there's # something wrong in the server. However in this case, KeyError means # invalid request, so it's best to return a client error. # For the HttpRpc case, internal error is 500 whereas # ResourceNotFoundError is 404. if not (userid in user_database): raise ResourceNotFoundError(userid) return user_database[userid] @rpc(User) def modify_user(ctx, user): global user_database if not (user.userid in user_database): raise ResourceNotFoundError(user.userid) user_database[user.userid] = user @rpc(Integer) def delete_user(ctx, userid): global user_database if not (userid in user_database): raise ResourceNotFoundError(userid) del user_database[userid] @rpc(_returns=Array(User)) def list_users(ctx): global user_database return user_database.values() if __name__ == '__main__': from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) application = Application([UserManager], 'spyne.examples.complex', in_protocol=HttpRpc(), out_protocol=XmlDocument()) server = make_server('127.0.0.1', 8000, WsgiApplication(application)) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/custom_type.py000066400000000000000000000057131417664205300206510ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne import ComplexModel, AnyDict, ValidationError, Array, Any from spyne.util import six from spyne.util.dictdoc import json_loads from spyne.util.web import log_repr class DictOfUniformArray(AnyDict): # warning! it's not a classmethod! @staticmethod def validate_native(cls, inst): for k, v in inst.items(): if not isinstance(k, six.string_types): raise ValidationError(type(k), "Invalid key type %r") if not isinstance(v, list): raise ValidationError(type(v), "Invalid value type %r") # log_repr prevents too much data going in the logs. if not len({type(subv) for subv in v}) == 1: raise ValidationError(log_repr(v), "List %s is not uniform") return True class Wrapper(ComplexModel): data = DictOfUniformArray # This example throws a validation error. Remove "invalid" entries from the data # dict to make it work. data = b""" { "data" : { "key_1" : [123, 567], "key_2" : ["abc", "def"], "frank_underwood" : [666.66, 333.333], "invalid": [123, "aaa"], "invalid_type": {"life": 42} } } """ print json_loads(data, Wrapper, validator='soft') # Expected output: # Wrapper(data={'frank_underwood': [666.66, 333.333], 'key_1': [123, 567], 'key_2': ['abc', 'def']}) spyne-spyne-2.14.0/examples/dict_skip.py000077500000000000000000000043721417664205300202520ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne import Boolean, String, ComplexModel from spyne.util.dictdoc import get_object_as_dict class SomeMapping(ComplexModel): compact = Boolean solid = Boolean object_name = String attached = Boolean is_published = Boolean is_owner = Boolean can_delete = Boolean can_insert= Boolean can_update = Boolean print(list(get_object_as_dict(SomeMapping( compact=True, solid=True, object_name="Cat", attached=False, is_published=True, is_owner=False, can_delete=False, can_insert= True, can_update=True ), SomeMapping, ignore_wrappers=True, complex_as=list))) spyne-spyne-2.14.0/examples/django/000077500000000000000000000000001417664205300171605ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/django/manage.py000077500000000000000000000003721417664205300207670ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rpctest.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) spyne-spyne-2.14.0/examples/django/rpctest/000077500000000000000000000000001417664205300206445ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/django/rpctest/__init__.py000066400000000000000000000031141417664205300227540ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © BJ Cardon , # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # spyne-spyne-2.14.0/examples/django/rpctest/core/000077500000000000000000000000001417664205300215745ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/django/rpctest/core/__init__.py000066400000000000000000000000001417664205300236730ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/django/rpctest/core/models.py000066400000000000000000000050131417664205300234300ustar00rootroot00000000000000# coding: utf-8 """Rpc test models.""" from django.core.validators import MinLengthValidator, MaxLengthValidator from django.db import models class FieldContainer(models.Model): """Test model for ``DjangoMapper``.""" char_field = models.CharField(max_length=32, default='test') char_field_nullable = models.CharField(max_length=32, null=True) slug_field = models.SlugField(max_length=32, unique=True) text_field = models.TextField(default='text_field') email_field = models.EmailField() boolean_field = models.BooleanField(default=True) integer_field = models.IntegerField(default=1) positive_integer_field = models.PositiveIntegerField(default=1) float_field = models.FloatField(default=1) decimal_field = models.DecimalField(max_digits=10, decimal_places=4, default=1) time_field = models.TimeField(auto_now_add=True) date_field = models.DateField(auto_now_add=True) datetime_field = models.DateTimeField(auto_now_add=True) foreign_key = models.ForeignKey('self', null=True, related_name='related_containers', on_delete=models.CASCADE) one_to_one_field = models.OneToOneField('self', null=True, on_delete=models.CASCADE) custom_foreign_key = models.ForeignKey('RelatedFieldContainer', null=True, related_name='related_fieldcontainers', on_delete=models.CASCADE) custom_one_to_one_field = models.OneToOneField('RelatedFieldContainer', null=True, on_delete=models.CASCADE) url_field = models.URLField(default='http://example.com') file_field = models.FileField(upload_to='test_file', null=True) excluded_field = models.CharField(max_length=32, default='excluded') blank_field = models.CharField(max_length=32, blank=True) length_validators_field = models.CharField( max_length=32, null=True, validators=[MinLengthValidator(3), MaxLengthValidator(10)]) class RelatedFieldContainer(models.Model): """Related container model to test related fields.""" id = models.CharField(max_length=30, primary_key=True) class User(models.Model): """Model for tests of relation field mapper.""" name = models.CharField(max_length=50) class UserProfile(models.Model): """Related model for tests of relation field mapper.""" user = models.ForeignKey(User, on_delete=models.CASCADE) data = models.CharField(max_length=50) spyne-spyne-2.14.0/examples/django/rpctest/core/tests.py000077700000000000000000000000001417664205300330272../../../../spyne/test/interop/test_django.pyustar00rootroot00000000000000spyne-spyne-2.14.0/examples/django/rpctest/core/views.py000066400000000000000000000072121417664205300233050ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © BJ Cardon , # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from django.core.exceptions import ValidationError from django.db.utils import IntegrityError from django.views.decorators.csrf import csrf_exempt from spyne.error import ResourceNotFoundError, ResourceAlreadyExistsError from spyne.server.django import DjangoApplication from spyne.model.primitive import Unicode, Integer from spyne.model.complex import Iterable from spyne.service import Service from spyne.protocol.soap import Soap11 from spyne.application import Application from spyne.decorator import rpc from spyne.util.django import DjangoComplexModel, DjangoService from rpctest.core.models import FieldContainer class Container(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = FieldContainer django_exclude = ['excluded_field'] class HelloWorldService(Service): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): for i in range(times): yield 'Hello, %s' % name class ContainerService(Service): @rpc(Integer, _returns=Container) def get_container(ctx, pk): try: return FieldContainer.objects.get(pk=pk) except FieldContainer.DoesNotExist: raise ResourceNotFoundError('Container') @rpc(Container, _returns=Container) def create_container(ctx, container): try: return FieldContainer.objects.create(**container.as_dict()) except IntegrityError: raise ResourceAlreadyExistsError('Container') class ExceptionHandlingService(DjangoService): """Service for testing exception handling.""" @rpc(_returns=Container) def raise_does_not_exist(ctx): return FieldContainer.objects.get(pk=-1) @rpc(_returns=Container) def raise_validation_error(ctx): raise ValidationError(None, 'Invalid.') app = Application([HelloWorldService, ContainerService, ExceptionHandlingService], 'spyne.examples.django', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(), ) hello_world_service = csrf_exempt(DjangoApplication(app)) spyne-spyne-2.14.0/examples/django/rpctest/settings.py000066400000000000000000000144421417664205300230630ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © BJ Cardon , # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Django settings for rpctest project. import os import tempfile DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS db_name = os.path.join(tempfile.gettempdir(), 'rpctest.sqlite') ALLOWED_HOSTS = ['newtestserver'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 'NAME': db_name, # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } } # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = 'en-us' SITE_ID = 1 # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale USE_L10N = True # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = '' # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" STATIC_ROOT = '' # URL prefix for static files. # Example: "http://media.lawrence.com/static/" STATIC_URL = '/static/' # URL prefix for admin static files -- CSS, JavaScript and images. # Make sure to use a trailing slash. # Examples: "http://foo.com/static/admin/", "/static/admin/". ADMIN_MEDIA_PREFIX = '/static/admin/' # Additional locations of static files STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) # Make this unique, and don't share it with anybody. SECRET_KEY = '#pa7-0yh+(9a)p(_r$3&^j5&5f3sal-m_8+r*k-_ft@4#)la(6' # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', ) ROOT_URLCONF = 'rpctest.urls' TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) INSTALLED_APPS = ( 'rpctest.core', # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', ) # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to # the site admins on every HTTP 500 error. # See http://docs.djangoproject.com/en/dev/topics/logging for # more details on how to customize your logging configuration. LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } spyne-spyne-2.14.0/examples/django/rpctest/urls.py000066400000000000000000000045431417664205300222110ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © BJ Cardon , # Burak ARrslan , # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from django.conf.urls import url from spyne.protocol.soap import Soap11 from spyne.server.django import DjangoView from rpctest.core.views import hello_world_service, app, HelloWorldService urlpatterns = [ url(r'^hello_world/', hello_world_service), url(r'^say_hello/', DjangoView.as_view( services=[HelloWorldService], tns='spyne.examples.django', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11())), url(r'^say_hello_not_cached/', DjangoView.as_view( services=[HelloWorldService], tns='spyne.examples.django', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(), cache_wsdl=False)), url(r'^api/', DjangoView.as_view(application=app)), ] spyne-spyne-2.14.0/examples/events.py000077500000000000000000000126531417664205300176060ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This example is an enhanced version of the HelloWorld example that uses event listeners to apply cross-cutting behavior to the service. In this example, the service hooks are used to gather performance information on both the method execution as well as the duration of the entire call, including serialization and deserialization. Events can be used for doing things like like database transaction management, logging and measuring performance. This example also uses the user-defined context (udc) attribute of the MethodContext object to hold the data points for this request. Use: curl 'http://localhost:8000/say_hello?name=Dave×=5' to query this code. """ import logging from time import time from spyne import Application, rpc, Service, String, Integer from spyne.server.wsgi import WsgiApplication from spyne.protocol.json import JsonDocument from spyne.protocol.http import HttpRpc class UserDefinedContext(object): def __init__(self): self.call_start = time() self.call_end = None self.method_start = None self.method_end = None class HelloWorldService(Service): @rpc(String, Integer, _returns=String(max_occurs='unbounded')) def say_hello(ctx, name, times): results = [] for i in range(0, times): results.append('Hello, %s' % name) return results def _on_wsgi_call(ctx): print("_on_wsgi_call") ctx.udc = UserDefinedContext() def _on_method_call(ctx): print("_on_method_call") ctx.udc.method_start = time() def _on_method_return_object(ctx): print("_on_method_return_object") ctx.udc.method_end = time() def _on_wsgi_return(ctx): print("_on_wsgi_return") call_end = time() print('Method took [%0.8f] - total execution time[%0.8f]'% ( ctx.udc.method_end - ctx.udc.method_start, call_end - ctx.udc.call_start)) def _on_wsgi_close(ctx): print("_on_wsgi_close: request processing completed.") def _on_method_context_destroyed(ctx): print("_on_method_context_destroyed") print('MethodContext(%d) lived for [%0.8f] seconds' % (id(ctx), ctx.call_end - ctx.call_start)) def _on_method_context_constructed(ctx): print("_on_method_context_constructed") print('Hello, this is MethodContext(%d). Time now: %0.8f' % (id(ctx), ctx.call_start)) if __name__=='__main__': logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) try: from wsgiref.simple_server import make_server except ImportError: logging.error("Error: example server code requires Python >= 2.5") application = Application([HelloWorldService], 'spyne.examples.events', in_protocol=HttpRpc(), out_protocol=JsonDocument()) application.event_manager.add_listener('method_call', _on_method_call) application.event_manager.add_listener('method_return_object', _on_method_return_object) application.event_manager.add_listener('method_context_constructed', _on_method_context_constructed) application.event_manager.add_listener('method_context_destroyed', _on_method_context_destroyed) wsgi_wrapper = WsgiApplication(application) wsgi_wrapper.event_manager.add_listener('wsgi_call', _on_wsgi_call) wsgi_wrapper.event_manager.add_listener('wsgi_return', _on_wsgi_return) wsgi_wrapper.event_manager.add_listener('wsgi_close', _on_wsgi_close) server = make_server('127.0.0.1', 8000, wsgi_wrapper) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/existing_api.py000077500000000000000000000055541417664205300207670ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging import random from spyne import Application, srpc, Service, Integer, UnsignedInteger, \ Mandatory as M from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication class RandomService(Service): # We need the _args argument here because we only want to expose the # `a` and `b` arguments and not the `self` argument. randint = srpc(M(Integer), M(Integer), _returns=Integer, _args=('a', 'b'))(random.randint) # We need the _args argument here because `getrandbits` is a builtin, which # means it's not ready for introspection. randbits = srpc(M(UnsignedInteger), _returns=UnsignedInteger, _args=('k',))(random.getrandbits) if __name__=='__main__': from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) application = Application([RandomService], 'spyne.examples.hello.http', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc()) server = make_server('127.0.0.1', 8000, WsgiApplication(application)) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/file_manager/000077500000000000000000000000001417664205300203275ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/file_manager/server.py000077500000000000000000000111171417664205300222130ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logger = logging.getLogger(__name__) import os from werkzeug.wsgi import DispatcherMiddleware from werkzeug.exceptions import NotFound from werkzeug.serving import run_simple from spyne.application import Application from spyne.decorator import rpc from spyne.service import Service from spyne.error import ResourceNotFoundError from spyne.error import ValidationError from spyne.model.binary import ByteArray from spyne.model.binary import File from spyne.model.primitive import Unicode from spyne.model.primitive import Mandatory from spyne.server.wsgi import WsgiApplication from spyne.protocol.http import HttpRpc BLOCK_SIZE = 8192 port = 9000 class FileServices(Service): @rpc(Mandatory.Unicode, _returns=ByteArray) def get(ctx, file_name): path = os.path.join(os.path.abspath('./files'), file_name) if not path.startswith(os.path.abspath('./files')): raise ValidationError(file_name) try: f = open(path, 'r') except IOError: raise ResourceNotFoundError(file_name) ctx.transport.resp_headers['Content-Disposition'] = ( 'attachment; filename=%s;' % file_name) data = f.read(BLOCK_SIZE) while len(data) > 0: yield data data = f.read(BLOCK_SIZE) f.close() @rpc(Unicode, Unicode, File.customize(min_occurs=1, nullable=False), _returns=Unicode) def add(ctx, person_type, action, file): logger.info("Person Type: %r" % person_type) logger.info("Action: %r" % action) path = os.path.join(os.path.abspath('./files'), file.name) if not path.startswith(os.path.abspath('./files')): raise ValidationError(file.name) f = open(path, 'w') # if this fails, the client will see an # internal error. try: for data in file.data: f.write(data) logger.debug("File written: %r" % file.name) f.close() except: f.close() os.remove(file.name) logger.debug("File removed: %r" % file.name) raise # again, the client will see an internal error. return "Tamam." def main(): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.wsgi').setLevel(logging.DEBUG) filemgr_app = WsgiApplication(Application([FileServices], tns='spyne.examples.file_manager', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc() )) try: os.makedirs('./files') except OSError: pass wsgi_app = DispatcherMiddleware(NotFound(), {'/filemgr': filemgr_app}) logger.info("navigate to: http://localhost:9000/index.html") return run_simple('localhost', port, wsgi_app, static_files={'/': 'static'}, threaded=True) if __name__ == '__main__': import sys sys.exit(main()) spyne-spyne-2.14.0/examples/file_manager/soap_server.py000077500000000000000000000114061417664205300232360ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """As the Xml Schema standard does not define a file primitive, the File type in the HTTP example does not work with Soap11 protocol. This is how you should handle binary data with Soap in Spyne. There is MTOM code inside Spyne but it lacks tests so it is not working as of now. """ import logging logger = logging.getLogger(__name__) import os from werkzeug.wsgi import DispatcherMiddleware from werkzeug.exceptions import NotFound from werkzeug.serving import run_simple from spyne.application import Application from spyne.decorator import rpc from spyne.service import Service from spyne.error import ResourceNotFoundError from spyne.error import ValidationError from spyne.model.binary import ByteArray from spyne.model.primitive import Unicode from spyne.model.primitive import Mandatory from spyne.server.wsgi import WsgiApplication from spyne.protocol.soap import Soap11 BLOCK_SIZE = 8192 port = 9000 class FileServices(Service): @rpc(Mandatory.Unicode, _returns=ByteArray(encoding='hex')) def get(ctx, file_name): path = os.path.join(os.path.abspath('./files'), file_name) if not path.startswith(os.path.abspath('./files')): raise ValidationError(file_name) try: f = open(path, 'rb') except IOError: raise ResourceNotFoundError(file_name) ctx.transport.resp_headers['Content-Disposition'] = ( 'attachment; filename=%s;' % file_name) data = f.read(BLOCK_SIZE) while len(data) > 0: yield data data = f.read(BLOCK_SIZE) f.close() @rpc(Unicode, Unicode, Unicode, ByteArray(min_occurs=1, nullable=False)) def add(ctx, person_type, action, file_name, file_data): logger.info("Person Type: %r" % person_type) logger.info("Action: %r" % action) path = os.path.join(os.path.abspath('./files'), file_name) if not path.startswith(os.path.abspath('./files')): raise ValidationError(file_name) f = open(path, 'wb') # if this fails, the client will see an # internal error. try: for data in file_data: f.write(data) logger.debug("File written: %r" % file_name) f.close() except: f.close() os.remove(path) logger.debug("File removed: %r" % file_name) raise # again, the client will see an internal error. def main(): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) filemgr_app = WsgiApplication(Application([FileServices], tns='spyne.examples.file_manager', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() )) try: os.makedirs('./files') except OSError: pass wsgi_app = DispatcherMiddleware(NotFound(), {'/filemgr': filemgr_app}) return run_simple('localhost', port, wsgi_app, static_files={'/': 'static'}, threaded=True) if __name__ == '__main__': import sys sys.exit(main()) spyne-spyne-2.14.0/examples/file_manager/static/000077500000000000000000000000001417664205300216165ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/file_manager/static/index.html000066400000000000000000000002421417664205300236110ustar00rootroot00000000000000 Upload Test Simple form upload test.
spyne-spyne-2.14.0/examples/file_manager/static/simple/000077500000000000000000000000001417664205300231075ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/file_manager/static/simple/index.html000066400000000000000000000010461417664205300251050ustar00rootroot00000000000000 Spyne Upload Test
Choose a file to upload:
spyne-spyne-2.14.0/examples/file_manager/suds_client.py000077500000000000000000000006261417664205300232240ustar00rootroot00000000000000#!/usr/bin/env python from suds.client import Client import base64 # Suds does not support base64binary type, so we do the encoding manually. file_data = base64.b64encode('file_data') c=Client('http://localhost:9000/filemgr/?wsdl') c.service.add('x', 'y', 'file_name', file_data) print('file written.') print() print('incoming data:') return_data = c.service.get('file_name') print(repr(return_data)) spyne-spyne-2.14.0/examples/file_soap_http.py000077500000000000000000000064431417664205300213020ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """Try: $ curl http://localhost:8000/get_file?path=http_soap_file.py """ import os from spyne import Service, File, rpc, Application, Unicode, ValidationError from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication # When dealing with files, always have a designated file repository -- ie. # a well-known directory where you store user-generated files. # # Here we're using os.getcwd() only because it's convenient. You must have a # site-dependent absolute path that you preferably read from a config file # here. FILE_REPO = os.getcwd() class FileServices(Service): @rpc(Unicode, _returns=File) def get_file(ctx, path): # protect against file name injection attacks # note that this doesn't protect against symlinks. depending on the kind # of write access your clients have, this may or may not be a problem. if not os.path.abspath(path).startswith(FILE_REPO): raise ValidationError(path) return File.Value(path=path) application = Application([FileServices], 'spyne.examples.file.soap', in_protocol=HttpRpc(), out_protocol=Soap11()) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/flask/000077500000000000000000000000001417664205300170165ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/flask/README.rst000066400000000000000000000012651417664205300205110ustar00rootroot00000000000000This is an example for using Flask and Spyne together. It is very simple, and does nothing useful. Create virtualenv and install requirements: :: pip install Flask pip install -e . # install Spyne from working directory Run Flask web server: :: ./examples/flask/manage.py Try Flask views to make sure it works: :: curl -s http://127.0.0.1:5000/hello | python -m json.tool Here is a Spyne views call example: :: curl -s http://localhost:5000/soap/hello?name=Anton\×=3 | python -m json.tool The same view call, but without explicit name argument, to read default from Flask config: :: curl -s http://localhost:5000/soap/hello?times=3 | python -m json.tool spyne-spyne-2.14.0/examples/flask/apps/000077500000000000000000000000001417664205300177615ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/flask/apps/__init__.py000066400000000000000000000030541417664205300220740ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Anton Egorov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # spyne-spyne-2.14.0/examples/flask/apps/flasked.py000066400000000000000000000037151417664205300217520ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Anton Egorov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import json from flask import Flask, make_response, current_app app = Flask(__name__) app.config.from_object('settings') @app.route('/hello') def hello(): '''Sample Flask API view that ruturns JSON with some data from config. ''' response = make_response(json.dumps({ 'hello': current_app.config['HELLO'], })) response.headers['Content-Type'] = 'application/json' return response spyne-spyne-2.14.0/examples/flask/apps/spyned.py000066400000000000000000000057101417664205300216400ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Anton Egorov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne import Iterable, Integer, Unicode, rpc, Application, Service from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument class HelloWorldService(Service): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def hello(ctx, name, times): name = name or ctx.udc.config['HELLO'] for i in range(times): yield u'Hello, %s' % name class UserDefinedContext(object): def __init__(self, flask_config): self.config = flask_config def create_app(flask_app): """Creates SOAP services application and distribute Flask config into user con defined context for each method call. """ application = Application( [HelloWorldService], 'spyne.examples.flask', # The input protocol is set as HttpRpc to make our service easy to call. in_protocol=HttpRpc(validator='soft'), out_protocol=JsonDocument(ignore_wrappers=True), ) # Use `method_call` hook to pass flask config to each service method # context. But if you have any better ideas do it, make a pull request. # NOTE. I refuse idea to wrap each call into Flask application context # because in fact we inside Spyne app context, not the Flask one. def _flask_config_context(ctx): ctx.udc = UserDefinedContext(flask_app.config) application.event_manager.add_listener('method_call', _flask_config_context) return application spyne-spyne-2.14.0/examples/flask/manage.py000077500000000000000000000037611417664205300206320ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Anton Egorov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from werkzeug.middleware.dispatcher import DispatcherMiddleware from spyne.server.wsgi import WsgiApplication from apps import spyned from apps.flasked import app # SOAP services are distinct wsgi applications, we should use dispatcher # middleware to bring all aps together app.wsgi_app = DispatcherMiddleware(app.wsgi_app, { '/soap': WsgiApplication(spyned.create_app(app)) }) if __name__ == '__main__': app.run() spyne-spyne-2.14.0/examples/flask/settings.py000066400000000000000000000031121417664205300212250ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Anton Egorov # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # DEBUG = True HELLO = 'World' spyne-spyne-2.14.0/examples/helloworld_http.py000077500000000000000000000100741417664205300215070ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a Http api using Spyne. Here's a sample: $ curl http://localhost:8000/say_hello?name=Dave\×=3 ["Hello, Dave", "Hello, Dave", "Hello, Dave"] """ import logging from spyne import Application, rpc, ServiceBase, Iterable, UnsignedInteger, \ String from spyne.protocol.json import JsonDocument from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication class HelloWorldService(ServiceBase): @rpc(String, UnsignedInteger, _returns=Iterable(String)) def say_hello(ctx, name, times): """ Docstrings for service methods do appear as documentation in the interface documents. What fun! :param name: The name to say hello to :param times: The number of times to say hello :returns: An array of 'Hello, ' strings, repeated times. """ for i in range(times): yield 'Hello, %s' % name if __name__ == '__main__': # Python daemon boilerplate from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) # Instantiate the application by giving it: # * The list of services it should wrap, # * A namespace string. # * An input protocol. # * An output protocol. application = Application([HelloWorldService], 'spyne.examples.hello.http', # The input protocol is set as HttpRpc to make our service easy to # call. Input validation via the 'soft' engine is enabled. (which is # actually the the only validation method for HttpRpc.) in_protocol=HttpRpc(validator='soft'), # The ignore_wrappers parameter to JsonDocument simplifies the reponse # dict by skipping outer response structures that are redundant when # the client knows what object to expect. out_protocol=JsonDocument(ignore_wrappers=True), ) # Now that we have our application, we must wrap it inside a transport. # In this case, we use Spyne's standard Wsgi wrapper. Spyne supports # popular Http wrappers like Twisted, Django, Pyramid, etc. as well as # a ZeroMQ (REQ/REP) wrapper. wsgi_application = WsgiApplication(application) # More daemon boilerplate server = make_server('127.0.0.1', 8000, wsgi_application) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/helloworld_soap.py000077500000000000000000000064731417664205300215020ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a webservice using spyne, starting a server, and creating a service client. Here's how to call it using suds: #>>> from suds.client import Client #>>> c = Client('http://localhost:8000/?wsdl') #>>> c.service.say_hello('punk', 5) (stringArray){ string[] = "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", } #>>> """ from spyne import Application, rpc, ServiceBase, Iterable, Integer, Unicode from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication class HelloWorldService(ServiceBase): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): """Docstrings for service methods appear as documentation in the wsdl. What fun! @param name the name to say hello to @param times the number of times to say hello @return the completed array """ for i in range(times): yield u'Hello, %s' % name application = Application([HelloWorldService], 'spyne.examples.hello.soap', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/helloworld_soap_bare.py000077500000000000000000000061211417664205300224610ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a webservice using spyne, starting a server, and creating a service client. Here's how to call it using suds: >>> from suds.client import Client >>> c = Client('http://localhost:8000/?wsdl') >>> c.service.say_hello('punk', 5) (stringArray){ string[] = "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", } >>> """ from spyne import rpc, Service, ComplexModel, Iterable, Integer, Unicode from spyne.util.simple import wsgi_soap_application class HelloWorldService(Service): @rpc(Unicode, _returns=Iterable(Unicode), _body_style='bare') def say_hello(ctx, name): """ Docstrings for service methods appear as documentation in the wsdl what fun @param name the name to say hello to @param the number of times to say hello @return the completed array """ return u'Hello, %s' % name if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") wsgi_app = wsgi_soap_application([HelloWorldService], 'spyne.examples.hello.soap') server = make_server('127.0.0.1', 8000, wsgi_app) server.serve_forever() spyne-spyne-2.14.0/examples/helloworld_soap_suds_client.py000077500000000000000000000034121417664205300240640ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from suds.client import Client client = Client('http://localhost:8000/?wsdl', cache=None) print(client.service.say_hello(u'Jérôme', 5)) spyne-spyne-2.14.0/examples/helloworld_soap_unqual.py000077500000000000000000000073671417664205300230720ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a webservice using spyne, starting a server, and creating a service client. Here's how to call it using suds: #>>> from suds.client import Client #>>> c = Client('http://localhost:8000/?wsdl') #>>> c.service.say_hello('punk', 5) (stringArray){ string[] = "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", "Hello, punk", } #>>> """ from spyne import Application, rpc, ServiceBase, Iterable, Integer, Unicode from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.interface import Interface, InterfaceDocumentsBase, Wsdl11 class HelloWorldService(ServiceBase): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): """Docstrings for service methods appear as documentation in the wsdl. What fun! @param name the name to say hello to @param times the number of times to say hello @return the completed array """ for i in range(times): yield u'Hello, %s' % name class MyInterfaceDocuments(InterfaceDocumentsBase): def __init__(self, interface): assert isinstance(interface, Interface) super(MyInterfaceDocuments, self).__init__( interface=interface, wsdl11=Wsdl11(interface, element_form_default='unqualified'), ) application = Application([HelloWorldService], 'spyne.examples.hello.soap', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(), documents_container=MyInterfaceDocuments) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/log.py000077500000000000000000000023501417664205300170540ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import absolute_import from spyne import Service, rpc, Application, Fault from spyne.server.null import NullServer from spyne.util.color import G class SomeService(Service): @rpc() def server_exception(ctx): raise Exception("boo!") @rpc() def server_fault(ctx): raise Fault("Server", "boo and you know it!") @rpc() def client_fault(ctx): raise Fault("Client", "zzzz...") server = NullServer(Application([SomeService], 'spyne.examples.logging')) if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) logging.info(G("all fault tracebacks are logged")) logging.getLogger('spyne.application').setLevel(logging.DEBUG) try: server.service.server_exception() except: pass try: server.service.server_fault() except: pass try: server.service.client_fault() except: pass logging.info(G("client fault tracebacks are hidden")) logging.getLogger('spyne.application.client').setLevel(logging.CRITICAL) try: server.service.server_fault() except: pass try: server.service.client_fault() except: pass spyne-spyne-2.14.0/examples/msgpack_transport/000077500000000000000000000000001417664205300214575ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/msgpack_transport/client_json_document.py000077500000000000000000000107161417664205300262460ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ Raw socket client example for JsonDocument via MessagePack server. """ from __future__ import print_function, absolute_import # These are analogues from spyne.server.msgpack. What's IN_REQUEST there is # OUT_REQUEST here because an outgoing request from a client's perspective is an # incoming request from a server's perspective. IN_RESPONSE_NO_ERROR = 0 IN_RESPONSE_CLIENT_ERROR = 1 IN_RESPONSE_SERVER_ERROR = 2 OUT_REQUEST = 1 import socket import json import msgpack from spyne.util.six import BytesIO s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 5551)) request_document = {"say_hello": ["Dave", 5]} # Because the server's input protocol is JsonDocument, we serialize the # request document to json bytestream. request_bytestream = json.dumps(request_document) # Because our server side transport is msgpack, we put the request bytestream # inside a MessagePack document. request_wrapper_document = [OUT_REQUEST, request_bytestream] # and we serialize the request wrapper document to msgpack bytestream as well request_wrapper_bytestream = msgpack.packb(request_wrapper_document) # Some numbers to show how efficient this is: print("Raw message length:", len(request_bytestream)) print("Wrapped message length:", len(request_wrapper_bytestream)) print("Overhead:", len(request_wrapper_bytestream) - len(request_bytestream), "byte(s).") # which we push to the socket. s.sendall(request_wrapper_bytestream) # This is straight from Python example in msgpack.org in_buffer = msgpack.Unpacker() while True: # We wait for the full message to arrive. in_buffer.feed(s.recv(1)) # Again, straight from the Python example in MessagePack homepage. for msg in in_buffer: print("Raw response document:", msg) # There should be only one entry in the response dict. We ignore the # rest here but we could whine about invalid response just as well. resp_code, data = iter(msg.items()).next() # We finally parse the response. We should probably be doing a dict # lookup here instead. if resp_code == IN_RESPONSE_NO_ERROR: print("Success. Response: ", json.loads(data)) # now that we have the response in a structured format, we could # further deserialize it to a Python object, depending on our needs. elif resp_code == IN_RESPONSE_CLIENT_ERROR: print("Invalid Request. Details: ", json.loads(data)) elif resp_code == IN_RESPONSE_SERVER_ERROR: print("Internal Error. Details: ", json.loads(data)) else: print("Unknown response. Update the client. Additional data:", data) # As we only sent one request, we must break after # receiving its response. break else: continue break # break after receiving one message. See above for why. spyne-spyne-2.14.0/examples/msgpack_transport/client_msgpack_document.py000077500000000000000000000107341417664205300267220ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ Raw socket client example for MessagePackDocument via MessagePack server. """ from __future__ import print_function, absolute_import # These are analogues from spyne.server.msgpack. What's IN_REQUEST there is # OUT_REQUEST here because an outgoing request from a client's perspective is an # incoming request from a server's perspective. IN_RESPONSE_NO_ERROR = 0 IN_RESPONSE_CLIENT_ERROR = 1 IN_RESPONSE_SERVER_ERROR = 2 OUT_REQUEST = 1 import socket import msgpack from spyne.util.six import BytesIO s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 5550)) request_document = {"say_hello": ["Dave", 5]} # Because the server's input protocol is MessagePackDocument, we serialize the # request document to msgpack bytestream. request_bytestream = msgpack.packb(request_document) # Because our server side transport is msgpack, we put the request bytestream # inside another MessagePack document. request_wrapper_document = [OUT_REQUEST, request_bytestream] # and we serialize the request wrapper document to msgpack bytestream as well request_wrapper_bytestream = msgpack.packb(request_wrapper_document) # Some numbers to show how efficient this is: print("Raw message length:", len(request_bytestream)) print("Wrapped message length:", len(request_wrapper_bytestream)) print("Overhead:", len(request_wrapper_bytestream) - len(request_bytestream), "byte(s).") # which we push to the socket. s.sendall(request_wrapper_bytestream) # This is straight from Python example in msgpack.org in_buffer = msgpack.Unpacker() while True: # We wait for the full message to arrive. in_buffer.feed(s.recv(1)) # Again, straight from the Python example in MessagePack homepage. for msg in in_buffer: print("Raw response document:", msg) # There should be only one entry in the response dict. We ignore the # rest here but we could whine about invalid response just as well. resp_code, data = iter(msg.items()).next() # We finally parse the response. We should probably do a dict lookup # here. if resp_code == IN_RESPONSE_NO_ERROR: print("Success. Response: ", msgpack.unpackb(data)) # now that we have the response in a structured format, we could # further deserialize it to a Python object, depending on our needs. elif resp_code == IN_RESPONSE_CLIENT_ERROR: print("Invalid Request. Details: ", msgpack.unpackb(data)) elif resp_code == IN_RESPONSE_SERVER_ERROR: print("Internal Error. Details: ", msgpack.unpackb(data)) else: print("Unknown response. Update the client. Additional data:", data) # As we only sent one request, we must break after # receiving its response. break else: continue break # break after receiving one message. See above for why. spyne-spyne-2.14.0/examples/msgpack_transport/server_json_document.py000077500000000000000000000060621417664205300262750ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a MessagePack-via-vanilla-TCP-sockets server using the JsonDocument protocol. """ import logging from spyne import Application, rpc, Service, Iterable, UnsignedInteger, \ Unicode from spyne.protocol.json import JsonDocument from spyne.server.twisted.msgpack import TwistedMessagePackProtocolFactory from spyne.server.wsgi import WsgiApplication HOST = '127.0.0.1' PORT = 5551 class HelloWorldService(Service): @rpc(Unicode, UnsignedInteger, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): for i in range(times): yield u'Hello, %s' % name if __name__=='__main__': from twisted.internet import reactor from twisted.python import log # logging boilerplate logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) # set up application application = Application([HelloWorldService], 'spyne.examples.hello.msgpack', in_protocol=JsonDocument(), out_protocol=JsonDocument()) # set up transport factory = TwistedMessagePackProtocolFactory(application) # set up listening endpoint reactor.listenTCP(PORT, factory, interface=HOST) # run the reactor reactor.run() spyne-spyne-2.14.0/examples/msgpack_transport/server_msgpack_document.py000077500000000000000000000060421417664205300267470ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple HelloWorld example to show the basics of writing a MessagePack-via-vanilla-TCP-sockets server. """ import logging from spyne import Application, rpc, Service, Iterable, UnsignedInteger, \ Unicode from spyne.protocol.msgpack import MessagePackDocument from spyne.server.twisted.msgpack import TwistedMessagePackProtocolFactory from spyne.server.wsgi import WsgiApplication HOST = '127.0.0.1' PORT = 5550 class HelloWorldService(Service): @rpc(Unicode, UnsignedInteger, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): for i in range(times): yield u'Hello, %s' % name if __name__=='__main__': from twisted.internet import reactor from twisted.python import log # logging boilerplate logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) # set up application application = Application([HelloWorldService], 'spyne.examples.hello.msgpack', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) # set up transport factory = TwistedMessagePackProtocolFactory(application) # set up listening endpoint reactor.listenTCP(PORT, factory, interface=HOST) # run the reactor reactor.run() spyne-spyne-2.14.0/examples/multiple_protocols/000077500000000000000000000000001417664205300216555ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/multiple_protocols/clock.svg000066400000000000000000000200241417664205300234670ustar00rootroot00000000000000 image/svg+xml 2012-03-09 spyne-spyne-2.14.0/examples/multiple_protocols/presentation/000077500000000000000000000000001417664205300243705ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/multiple_protocols/presentation/Makefile000066400000000000000000000003211417664205300260240ustar00rootroot00000000000000 all: pres.pdf pres.pdf: pres.tex get_utc_time.pdf pdflatex pres.tex pdflatex pres.tex pdflatex pres.tex get_utc_time.pdf: get_utc_time.svg inkscape --file=get_utc_time.svg --export-pdf get_utc_time.pdf spyne-spyne-2.14.0/examples/multiple_protocols/presentation/get_utc_time.svg000066400000000000000000000146321417664205300275670ustar00rootroot00000000000000 image/svg+xml 2012-03-09 spyne-spyne-2.14.0/examples/multiple_protocols/presentation/pres.tex000066400000000000000000000320171417664205300260660ustar00rootroot00000000000000 \documentclass{beamer} \usepackage[utf8x]{inputenc} \usetheme{default} \usepackage{setspace} \usepackage{hyperref} \usepackage{listings} \definecolor{keywords}{RGB}{180,180,0} \definecolor{comments}{RGB}{60,179,113} % \definecolor{red}{RGB}{255,0,0} \definecolor{darkgreen}{RGB}{0,127,0} \lstset{ language=Python, frame=lines, keywordstyle=\color{keywords}, commentstyle=\color{comments}\emph, stringstyle=\ttfamily\color{darkgreen}, emph={Soap11,XmlDocument,HtmlMicroFormat},emphstyle=\color{red}\textbf } \title{A Quick Look At Spyne} \author{Burak Arslan \\ \small burak at arskom \small{dot} com \small{dot} tr} \usepackage{wasysym} \hypersetup{ colorlinks, citecolor=blue, filecolor=blue, linkcolor=blue, urlcolor=blue, breaklinks=true } \setbeamertemplate{footline}{\hfill\scriptsize{\insertframenumber}\hspace*{.5cm}\vspace*{.5cm}} \setbeamertemplate{navigation symbols}{} \begin{document} \begin{frame} \maketitle \end{frame} \begin{frame} \frametitle{What is Spyne?} \LARGE \begin{center} Spyne makes it convenient to \bigskip expose your services using multiple \bigskip protocols and/or transports. \end{center} \end{frame} % \begin{frame} % \LARGE % \begin{center} % % Which seems to be especially important % % \bigskip % % for applications with multiple types of clients % % \large % % \bigskip % % (Native apps, browsers, etc.) % % \end{center} % % \end{frame} \begin{frame} \frametitle{What is Spyne?} \LARGE \begin{center} It also forces you to have a well-defined api. % \bigskip % % \pause % % Because static typing works wonders % % \bigskip % % when your input comes from untrusted sources \end{center} \end{frame} \begin{frame} \Huge \begin{center} How? \end{center} \end{frame} \begin{frame}[fragile] \LARGE \begin{center} Here's a simple function: \bigskip \bigskip \large \begin{lstlisting} from datetime import datetime def get_utc_time(): return datetime.utcnow() \end{lstlisting} \end{center} \end{frame} \begin{frame} \LARGE Now, to make this function remotely callable; \bigskip \pause \color{red} \textbf{1)} \color{black} \begin{center} We wrap it in a \texttt{Service} subclass: \end{center} \end{frame} \begin{frame}[fragile] \begin{uncoverenv}<2-> \begin{lstlisting}[frame=none] from spyne.model.primitive import DateTime from spyne.decorator import srpc from spyne.service import Service \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<3-> \begin{lstlisting}[frame=none] class DateTimeService(Service): \end{lstlisting} \vspace{-13pt} \end{uncoverenv} \begin{uncoverenv}<4-> \begin{lstlisting}[frame=none] @srpc(_returns=DateTime) \end{lstlisting} \vspace{-13pt} \end{uncoverenv} \begin{uncoverenv}<1-> \begin{lstlisting}[frame=none] def get_utc_time(): return datetime.utcnow() \end{lstlisting} \end{uncoverenv} \end{frame} \begin{frame} \LARGE \color{red} \textbf{2)} \color{black} \begin{center} Now, we have to wrap the service definition \bigskip in an \texttt{Application} definition. \end{center} \end{frame} \begin{frame}[fragile] \begin{uncoverenv}<2-> \vspace{-13pt} \begin{lstlisting}[frame=none] from spyne.application import Application from spyne.protocol.http import HttpRpc \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<2-> \begin{lstlisting}[frame=none] httprpc = Application( \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<1-> \vspace{-13pt} \begin{lstlisting}[frame=none] [DateTimeService], \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<3-> \vspace{-13pt} \begin{lstlisting}[frame=none] tns='spyne.examples.multiprot', \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<4-> \vspace{-13pt} \begin{lstlisting}[frame=none] in_protocol=HttpRpc(), out_protocol=HttpRpc() ) \end{lstlisting} \end{uncoverenv} \end{frame} \begin{frame} \LARGE \color{red} \textbf{3)} \color{black} \begin{center} Finally, we wrap the application in \bigskip a transport. \end{center} \end{frame} \begin{frame}[fragile] \begin{lstlisting} from spyne.server.wsgi import WsgiApplication application = WsgiApplication(httprpc) \end{lstlisting} \begin{center} This is now a regular WSGI Application that \bigskip we can pass to WSGI-compliant servers like \bigskip CherryPy, mod\_wsgi, Twisted, etc. \end{center} \pause \begin{lstlisting}[language=sh] $ curl http://localhost:9910/get_utc_time 2012-03-09T17:38:11.997784 \end{lstlisting} \end{frame} \begin{frame} \LARGE \begin{center} Now, what if we wanted to expose this \bigskip function using another protocol? \end{center} \end{frame} \begin{frame}[fragile] \frametitle{For example: SOAP} \begin{lstlisting} from spyne.application import Application from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 soap = Application([DateTimeService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(), out_protocol=Soap11() ) \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{For example: SOAP} \begin{lstlisting}[language=sh,frame=topline] $ curl http://localhost:9910/get_utc_time \ | tidy -xml -indent \end{lstlisting} \tiny \begin{lstlisting}[language=xml,frame=bottomline] 2012-03-06T17:43:30.894466 \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Or, just XML:} \begin{lstlisting} from spyne.application import Application from spyne.protocol.http import HttpRpc from spyne.protocol.xml import XmlDocument xml = Application([DateTimeService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(), out_protocol=XmlDocument() ) \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Or, just XML:} \begin{lstlisting}[language=sh,frame=topline] $ curl http://localhost:9910/get_utc_time \ | tidy -xml -indent \end{lstlisting} \small \begin{lstlisting}[language=xml,frame=bottomline] 2012-03-06T17:49:08.922501 \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Or, HTML:} \begin{lstlisting} from spyne.application import Application from spyne.protocol.http import HttpRpc from spyne.protocol.xml import HtmlMicroFormat html = Application([DateTimeService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(), out_protocol=HtmlMicroFormat() ) \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{Or, HTML:} \begin{lstlisting}[language=sh,frame=topline] $ curl http://localhost:9910/get_utc_time \ | tidy -xml -indent \end{lstlisting} \begin{lstlisting}[language=html, frame=bottomline]
2012-03-06T17:52:50.234246
\end{lstlisting} \end{frame} \begin{frame} \LARGE \begin{center} etc... \end{center} \end{frame} \begin{frame} \LARGE \begin{center} Spyne also makes it easy to implement \bigskip custom protocols. \end{center} \end{frame} \begin{frame} \LARGE \begin{center} Let's implement an output protocol that \bigskip renders the datetime value as an analog \bigskip clock. \bigskip \large (without going into much detail \smiley) \end{center} \end{frame} \begin{frame} \LARGE \begin{center} To do that, we need to implement the \bigskip \texttt{serialize} and \texttt{create\_out\_string} \bigskip functions in a \texttt{ProtocolBase} subclass. \end{center} \end{frame} \begin{frame}[fragile] \small \begin{lstlisting}[frame=none] from lxml import etree from spyne.protocol import ProtocolBase class SvgClock(ProtocolBase): mime_type = 'image/svg+xml' \end{lstlisting} \begin{uncoverenv}<2-> \begin{lstlisting}[frame=none] def serialize(self, ctx, message): d = ctx.out_object[0] # the return value \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<3-> \begin{lstlisting}[frame=none] # (some math and boilerplate suppressed) \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<4-> \begin{lstlisting}[frame=none] # clock is a svg file parsed as lxml Element ctx.out_document = clock \end{lstlisting} \end{uncoverenv} \begin{uncoverenv}<5-> \begin{lstlisting}[frame=none] def create_out_string(self, ctx, charset=None): ctx.out_string = [ etree.tostring(ctx.out_document) ] \end{lstlisting} \end{uncoverenv} \end{frame} \begin{frame}[fragile] \frametitle{The custom SVG protocol:} \begin{lstlisting}[emph={SvgClock}] from spyne.application import Application svg = Application([DateTimeService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(), out_protocol=SvgClock() ) \end{lstlisting} \end{frame} \begin{frame}[fragile] \frametitle{The custom SVG protocol:} \begin{lstlisting}[language=sh] $ curl http://localhost:9910/get_utc_time \ > utc_time.svg \end{lstlisting} \begin{center} \includegraphics[scale=.4]{get_utc_time.pdf} \end{center} \end{frame} \begin{frame} \begin{center} \LARGE It's also easy to implement declarative \bigskip restrictions on your input data. \end{center} \end{frame} \begin{frame}[fragile] \begin{tabular}{l} \LARGE So instead of doing this: \end{tabular} \bigskip \begin{lstlisting} def get_name_of_month(month): """Takes an integer between 1-12 and returns the name of month as string """ value = int(month) if not (1 <= value <= 12): raise ValueError(value) return datetime(2000,month,1).strftime("%B") \end{lstlisting} \end{frame} \begin{frame}[fragile] \begin{tabular}{l} \LARGE You can do this: \end{tabular} \bigskip \begin{lstlisting}[emph={Integer,le,ge}] class NameOfMonthService(Service): @srpc(Integer(ge=1,le=12), _returns=Unicode) def get_name_of_month(month): return datetime(2000,month,1).strftime("%B") \end{lstlisting} \end{frame} \begin{frame}[fragile] \begin{tabular}{l} \LARGE And if you enable validation; \end{tabular} \bigskip \begin{lstlisting}[emph=validator] from spyne.application import Application from spyne.protocol.http import HttpRpc rest = Application([NameOfMonthService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc() ) \end{lstlisting} \end{frame} \begin{frame}[fragile] \begin{lstlisting}[language=sh] $ curl localhost:9912/get_name_of_month?month=3 March \end{lstlisting} \pause \bigskip \begin{lstlisting}[language=sh,basicstyle=\small] $ curl -D - localhost:9912/get_name_of_month?month=13 HTTP/1.0 400 Bad Request Date: Sat, 10 Mar 2012 14:21:36 GMT Server: WSGIServer/0.1 Python/2.7.2 Content-Length: 63 Content-Type: text/plain Client.ValidationError The string '13' could not be validated \end{lstlisting} \end{frame} \begin{frame} \huge \textbf{So, what's missing?} \begin{center} \Large \begin{tabular}{ll} \textbf{Protocols}: & JSON! ProtoBuf! XmlRpc! Thrift! \\ & YAML! HTML! (The whole document) \\ \textbf{Transports}: & SMTP! Files! SPDY! WebSockets! \end{tabular} \bigskip \large $\left( \begin{tabular}{c} and many other things! see the ROADMAP.rst \\ in the source repo. \end{tabular} \right)$ \end{center} \end{frame} \begin{frame} \frametitle{Additional Information:} \begin{center} \huge \href{http://github.com/arskom/spyne}{github.com/arskom/spyne} \bigskip \large This example and the presentation are in: \href{https://github.com/arskom/spyne/tree/master/examples/multiple_protocols}{examples/multiple\_protocols} \href{https://github.com/arskom/spyne/tree/master/examples/validation.py}{examples/validation.py} \bigskip Stay for the sprints! I'll be around! \end{center} \end{frame} \end{document} spyne-spyne-2.14.0/examples/multiple_protocols/protocol.py000066400000000000000000000112011417664205300240630ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logger = logging.getLogger(__name__) import copy from lxml import etree from spyne.protocol import ProtocolBase from spyne.model.primitive import DateTime clock = etree.fromstring(open('clock.svg','r').read()) ns = {'x': 'http://www.w3.org/2000/svg'} class SvgClock(ProtocolBase): mime_type = 'image/svg+xml' def __init__(self, app=None): super(SvgClock, self).__init__(app, validator=None) self.length = 500 # if you change this, you should re-scale the svg file # as well. def serialize(self, ctx, message): """Uses a datetime.datetime instance inside ctx.out_object[0] to set ctx.out_document to an lxml.etree._Element instance. """ # this is an output-only protocol assert message in (self.RESPONSE,) # this protocol can only handle DateTime types. return_type = ctx.descriptor.out_message._type_info[0] assert issubclass(return_type, DateTime), \ "This protocol only supports functions with %r as return " \ "type" % DateTime # Finally, start serialization. self.event_manager.fire_event('before_serialize', ctx) ctx.out_header_doc = None ctx.out_body_doc = copy.deepcopy(clock) d = ctx.out_object[0] # this has to be a datetime.datetime instance. # set the current date ctx.out_body_doc.xpath("//x:tspan[@id='date_text']", namespaces=ns)[0] \ .text = '%04d-%02d-%02d' % (d.year, d.month, d.day) minute_hand = d.minute * 360 / 60; hour_hand = (d.hour % 12) * 360.0 / 12 + minute_hand / 12; ctx.out_body_doc.xpath("//x:path[@id='akrep']", namespaces=ns)[0] \ .attrib['transform'] += \ ' rotate(%d, %d, %d)' % (hour_hand, self.length /2, self.length / 2) ctx.out_body_doc.xpath("//x:path[@id='yelkovan']", namespaces=ns)[0] \ .attrib['transform'] += \ ' rotate(%d, %d, %d)' % (minute_hand, self.length /2, self.length /2) ctx.out_document = ctx.out_body_doc self.event_manager.fire_event('after_serialize', ctx) def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string""" ctx.out_string = [etree.tostring(ctx.out_document, pretty_print=True, encoding='utf8', xml_declaration=True)] def decompose_incoming_envelope(self, ctx, message): raise NotImplementedError("This is an output-only protocol.") class PngClock(SvgClock): mime_type = 'image/png' def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string""" import rsvg from gtk import gdk h = rsvg.Handle() h.write(etree.tostring(ctx.out_document)) h.close() pixbuf = h.get_pixbuf() ctx.out_string = [] def cb(buf, data=None): ctx.out_string.append(buf) return True pixbuf.save_to_callback(cb, 'png') spyne-spyne-2.14.0/examples/multiple_protocols/server.py000077500000000000000000000152001417664205300235360ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """This is a gallery of protocols. Visit the following urls in a browser to get the same information in different protocols.: http://localhost:9910/xml/get_utc_time http://localhost:9910/html/get_utc_time http://localhost:9910/rest/get_utc_time http://localhost:9910/soap/get_utc_time http://localhost:9910/png/get_utc_time http://localhost:9910/svg/get_utc_time http://localhost:9910/json/get_utc_time http://localhost:9910/jsoni/get_utc_time http://localhost:9910/jsonl/get_utc_time http://localhost:9910/jsonil/get_utc_time http://localhost:9910/mpo/get_utc_time http://localhost:9910/mpr/get_utc_time http://localhost:9910/yaml/get_utc_time You need python bindings for librsvg for svg & png protocols. # debian/ubuntu apt-get install python-rsvg # gentoo emerge librsvg-python along with every other otherwise optional Spyne dependency. """ import logging from datetime import datetime from protocol import PngClock from protocol import SvgClock from spyne import Application, rpc, srpc, DateTime, String, Service from spyne.protocol.html import HtmlMicroFormat from spyne.protocol.http import HttpPattern, HttpRpc from spyne.protocol.json import JsonDocument from spyne.protocol.msgpack import MessagePackDocument, MessagePackRpc from spyne.protocol.soap import Soap11 from spyne.protocol.xml import XmlDocument from spyne.protocol.yaml import YamlDocument from spyne.util.wsgi_wrapper import WsgiMounter tns = 'spyne.examples.multiple_protocols' port = 9910 host = '127.0.0.1' class MultiProtService(Service): @srpc(_returns=DateTime) def get_utc_time(): return datetime.utcnow() def Tsetprot(prot): def setprot(ctx): ctx.out_protocol = prot return setprot class DynProtService(Service): protocols = {} @rpc(String(values=protocols.keys(), encoding='ascii'), _returns=DateTime, _patterns=[HttpPattern('/get_utc_time\\.')]) def get_utc_time(ctx, prot): DynProtService.protocols[prot](ctx) return datetime.utcnow() def main(): rest = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=HttpRpc()) xml = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=XmlDocument()) soap = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=Soap11()) html = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=HtmlMicroFormat()) png = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=PngClock()) svg = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=SvgClock()) json = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=JsonDocument()) jsoni = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=JsonDocument(ignore_wrappers=False)) jsonl = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=JsonDocument(complex_as=list)) jsonil = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=JsonDocument(ignore_wrappers=False, complex_as=list)) msgpack_obj = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=MessagePackDocument()) msgpack_rpc = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=MessagePackRpc()) yaml = Application([MultiProtService], tns=tns, in_protocol=HttpRpc(), out_protocol=YamlDocument()) dyn = Application([DynProtService], tns=tns, in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc()) DynProtService.protocols = { 'json': Tsetprot(JsonDocument(dyn)), 'xml': Tsetprot(XmlDocument(dyn)), 'yaml': Tsetprot(YamlDocument(dyn)), 'soap': Tsetprot(Soap11(dyn)), 'html': Tsetprot(HtmlMicroFormat(dyn)), 'png': Tsetprot(PngClock(dyn)), 'svg': Tsetprot(SvgClock(dyn)), 'msgpack': Tsetprot(MessagePackDocument(dyn)), } root = WsgiMounter({ 'rest': rest, 'xml': xml, 'soap': soap, 'html': html, 'png': png, 'svg': svg, 'json': json, 'jsoni': jsoni, 'jsonl': jsonl, 'jsonil': jsonil, 'mpo': msgpack_obj, 'mpr': msgpack_rpc, 'yaml': yaml, 'dyn': dyn, }) from wsgiref.simple_server import make_server server = make_server(host, port, root) logging.basicConfig(level=logging.DEBUG) logging.info("listening to http://%s:%d" % (host, port)) logging.info("navigate to e.g. http://%s:%d/json/get_utc_time" % (host, port)) logging.info(" or: http://%s:%d/xml/get_utc_time" % (host, port)) return server.serve_forever() if __name__ == '__main__': import sys sys.exit(main()) spyne-spyne-2.14.0/examples/override.py000077500000000000000000000057511417664205300201220ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This example shows how to override the variable names for fun and profit. This is very useful for situations that require the use of variable names that are python keywords like from, import, return, etc. """ from spyne import Application, rpc, Service, String, ComplexModel from spyne.util.odict import odict from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication class SomeClass(ComplexModel): _type_info = odict([ ('and', String), ('or', String), ]) class EmailManager(Service): @rpc(String, String, String, _returns=String, _in_variable_names={'from_': 'from'}, _out_variable_name='return') def send_email(ctx, to, from_, message): # do email sending here return repr((to, from_, message, 'sent!')) application = Application([EmailManager], 'spyne.examples.events', in_protocol=Soap11(), out_protocol=Soap11()) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) server = make_server('127.0.0.1', 8000, WsgiApplication(application)) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/pyramid/000077500000000000000000000000001417664205300173635ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/pyramid/helloworld_http.py000077500000000000000000000075271417664205300231650ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging from pyramid.config import Configurator from pyramid.view import view_config from wsgiref.simple_server import make_server from spyne.util.simple import pyramid_soap11_application from spyne.decorator import rpc from spyne.model.complex import Iterable from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.service import Service logging.basicConfig() logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) tns = 'spyne.examples.pyramid.helloworld' ''' >>> from suds.client import Client >>> cli = Client("http://localhost:8000/?wsdl") >>> cli >>> print cli Suds ( https://fedorahosted.org/suds/ ) version: 0.4 GA build: R699-20100913 Service ( HelloWorldService ) tns="spyne.helloworld" Prefixes (1) ns0 = "spyne.helloworld" Ports (1): (Application) Methods (1): say_hello(xs:string name, xs:integer times, ) Types (3): say_hello say_helloResponse stringArray >>> res = cli.service.say_hello('justin', 5) >>> res (stringArray){ string[] = "Hello, justin", "Hello, justin", "Hello, justin", "Hello, justin", "Hello, justin", } ''' class HelloWorldService(Service): __namespace__ = tns @rpc(String, Integer, _returns=Iterable(String)) def say_hello(ctx, name, times): ''' Docstrings for service methods appear as documentation in the wsdl what fun @param name the name to say hello to @param the number of times to say hello @return the completed array ''' for i in range(times): yield 'Hello, %s' % name # view soapApp = view_config(route_name="home")( pyramid_soap11_application([HelloWorldService], tns)) if __name__ == '__main__': # configuration settings settings = {} settings['debug_all'] = True # configuration setup config = Configurator(settings=settings) # routes setup config.add_route('home', '/') config.scan() # serve app app = config.make_wsgi_app() server = make_server('0.0.0.0', 8000, app) server.serve_forever() spyne-spyne-2.14.0/examples/queue.py000077500000000000000000000172501417664205300174240ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """This is a simple db-backed persistent task queue implementation. The producer (client) writes requests to a database table. The consumer (server) polls the database every 10 seconds and processes new requests. """ import time import logging from sqlalchemy import MetaData from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from spyne import MethodContext, Application, rpc, TTableModel, Integer32, \ UnsignedInteger, ByteArray, Mandatory as M # client stuff from spyne import RemoteService, RemoteProcedureBase, ClientBase # server stuff from spyne import ServerBase, Service from spyne.protocol.soap import Soap11 db = create_engine('sqlite:///:memory:') TableModel = TTableModel(MetaData(bind=db)) # # The database tables used to store tasks and worker status # class TaskQueue(TableModel): __tablename__ = 'task_queue' id = Integer32(primary_key=True) data = ByteArray(nullable=False) class WorkerStatus(TableModel): __tablename__ = 'worker_status' worker_id = Integer32(pk=True, autoincrement=False) task = TaskQueue.customize(store_as='table') # # The consumer (server) implementation # class Consumer(ServerBase): transport = 'http://sqlalchemy.persistent.queue/' def __init__(self, db, app, consumer_id): super(Consumer, self).__init__(app) self.session = sessionmaker(bind=db)() self.id = consumer_id if self.session.query(WorkerStatus).get(self.id) is None: self.session.add(WorkerStatus(worker_id=self.id)) self.session.commit() def serve_forever(self): while True: # get the id of the last processed job last = self.session.query(WorkerStatus).with_lockmode("update") \ .filter_by(worker_id=self.id).one() # get new tasks task_id = 0 if last.task is not None: task_id = last.task.id task_queue = self.session.query(TaskQueue) \ .filter(TaskQueue.id > task_id) \ .order_by(TaskQueue.id) for task in task_queue: initial_ctx = MethodContext(self) # this is the critical bit, where the request bytestream is put # in the context so that the protocol can deserialize it. initial_ctx.in_string = [task.data] # these two lines are purely for logging initial_ctx.transport.consumer_id = self.id initial_ctx.transport.task_id = task.id # The ``generate_contexts`` call parses the incoming stream and # splits the request into header and body parts. # There will be only one context here because no auxiliary # methods are defined. for ctx in self.generate_contexts(initial_ctx, 'utf8'): # This is standard boilerplate for invoking services. self.get_in_object(ctx) if ctx.in_error: self.get_out_string(ctx) logging.error(''.join(ctx.out_string)) continue self.get_out_object(ctx) if ctx.out_error: self.get_out_string(ctx) logging.error(''.join(ctx.out_string)) continue self.get_out_string(ctx) logging.debug(''.join(ctx.out_string)) last.task_id = task.id self.session.commit() time.sleep(10) # # The producer (client) implementation # class RemoteProcedure(RemoteProcedureBase): def __init__(self, db, app, name, out_header): super(RemoteProcedure, self).__init__(db, app, name, out_header) self.Session = sessionmaker(bind=db) def __call__(self, *args, **kwargs): session = self.Session() for ctx in self.contexts: self.get_out_object(ctx, args, kwargs) self.get_out_string(ctx) out_string = ''.join(ctx.out_string) print(out_string) session.add(TaskQueue(data=out_string)) session.commit() session.close() class Producer(ClientBase): def __init__(self, db, app): super(Producer, self).__init__(db, app) self.service = RemoteService(RemoteProcedure, db, app) # # The service to call. # class AsyncService(Service): @rpc(M(UnsignedInteger)) def sleep(ctx, integer): print("Sleeping for %d seconds..." % (integer)) time.sleep(integer) def _on_method_call(ctx): print("This is worker id %d, processing task id %d." % ( ctx.transport.consumer_id, ctx.transport.task_id)) AsyncService.event_manager.add_listener('method_call', _on_method_call) if __name__ == '__main__': # set up logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) # Setup colorama and termcolor, if they are there try: from termcolor import colored from colorama import init init() except ImportError, e: logging.error("Install 'termcolor' and 'colorama' packages to get " "colored log output") def colored(s, *args, **kwargs): return s logging.info(colored("Creating database tables...", 'yellow', attrs=['bold'])) TableModel.Attributes.sqla_metadata.create_all() logging.info(colored("Creating Application...", 'blue')) application = Application([AsyncService], 'spyne.async', in_protocol=Soap11(), out_protocol=Soap11()) logging.info(colored("Making requests...", 'yellow', attrs=['bold'])) producer = Producer(db, application) for i in range(10): producer.service.sleep(i) logging.info(colored("Spawning consumer...", 'blue')) # process requests. it'd make most sense if this was in another process. consumer = Consumer(db, application, consumer_id=1) consumer.serve_forever() spyne-spyne-2.14.0/examples/response_as_file_dynamic.py000077500000000000000000000111611417664205300233170ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne import Application, rpc, Service, Iterable, Integer, Unicode, \ File from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument from spyne.protocol.xml import XmlDocument from spyne.server.http import HttpTransportContext from spyne.server.wsgi import WsgiApplication def _say_hello(ctx, name, times, file_ext): if isinstance(ctx.transport, HttpTransportContext): file_name = "{}.{}".format(ctx.descriptor.name, file_ext) ctx.transport.add_header('Content-Disposition', 'attachment', filename=file_name) for i in range(times): yield u'Hello, %s' % name class SomeService(Service): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello_as_xml_file(ctx, name, times): ctx.out_protocol = XmlDocument() # this is normally set automatically based on the out protocol # but you can set it just to be explicit if isinstance(ctx.transport, HttpTransportContext): ctx.transport.set_mime_type("application/xml") return _say_hello(ctx, name, times, 'xml') @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello_as_json_file(ctx, name, times): ctx.out_protocol = JsonDocument() # see how we don't set the mime type but it's still present in the # response headers return _say_hello(ctx, name, times ,'txt') @rpc(Unicode, Integer, _returns=Unicode) def say_hello_as_text_file(ctx, name, times): return '\n'.join(_say_hello(ctx, name, times ,'json')) @rpc(Unicode, Integer, _returns=File) def say_hello_as_binary_file(ctx, name, times): # WARNING!: the native value for data is an iterable of bytes, not just # bytes! If you forget this you may return data using 1-byte http chunks # which is incredibly inefficient. # WARNING!: don't forget to encode your data! This is the binary # output mode! You can't just write unicode data to socket! mime_type = HttpTransportContext.gen_header("text/plain", charset="utf8") return File.Value(type=mime_type, data=['\n'.join(s.encode('utf8') for s in _say_hello(ctx, name, times, 'txt'))] ) application = Application([SomeService], 'spyne.examples.response_as_file', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc()) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/response_as_xml_file.py000077500000000000000000000061121417664205300224730ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne import Application, rpc, Service, Iterable, Integer, Unicode from spyne.protocol.http import HttpRpc from spyne.protocol.xml import XmlDocument from spyne.server.http import HttpTransportContext from spyne.server.wsgi import WsgiApplication class SomeService(Service): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello_as_file(ctx, name, times): if isinstance(ctx.transport, HttpTransportContext): file_name = "{}.xml".format(ctx.descriptor.name) ctx.transport.set_mime_type("application/xml") # Force download ctx.transport.add_header('Content-Disposition', 'attachment', filename=file_name) for i in range(times): yield u'Hello, %s' % name application = Application([SomeService], 'spyne.examples.response_as_file', in_protocol=HttpRpc(validator='soft'), out_protocol=XmlDocument()) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/sql/000077500000000000000000000000001417664205300165155ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/sql/sql_crud.py000077500000000000000000000150401417664205300207060ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) import pytz from spyne import Application, rpc, Mandatory as M, Unicode, Array, Iterable, \ UnsignedInteger32, TTableModel, Service, ResourceNotFoundError, \ ComplexModel, UnsignedInteger8 from spyne.protocol.http import HttpRpc from spyne.protocol.yaml import YamlDocument from spyne.util import memoize from spyne.server.wsgi import WsgiApplication from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker db = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=db) TableModel = TTableModel() TableModel.Attributes.sqla_metadata.bind = db class Preferences(ComplexModel): _type_info = [ ('timezone', M(Unicode(values=pytz.all_timezones_set))), ('font_size', UnsignedInteger8( values_dict={10: 'Small', 20: 'Medium', 30: 'Large'}, default=20, )), ('theme', Unicode( values={'dark', 'light'}, default='light', )), ] class Permission(TableModel): __tablename__ = 'permission' __namespace__ = 'spyne.examples.sql_crud' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(primary_key=True) application = Unicode(256) operation = Unicode(256) class User(TableModel): __tablename__ = 'user' __namespace__ = 'spyne.examples.sql_crud' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(primary_key=True) name = Unicode(256) first_name = Unicode(256) last_name = Unicode(256) permissions = Array(Permission, store_as='table') preferences = Preferences.store_as('jsonb') @memoize def TCrudService(T, T_name): class CrudService(Service): @rpc(M(UnsignedInteger32), _returns=T, _in_message_name='get_%s' % T_name, _in_variable_names={'obj_id': "%s_id" % T_name}) def get(ctx, obj_id): return ctx.udc.session.query(T).filter_by(id=obj_id).one() @rpc(M(T), _returns=UnsignedInteger32, _in_message_name='put_%s' % T_name, _in_variable_names={'obj': T_name}) def put(ctx, obj): if obj.id is None: ctx.udc.session.add(obj) ctx.udc.session.flush() # so that we get the obj.id value else: if ctx.udc.session.query(T).get(obj.id) is None: # this is to prevent the client from setting the primary key # of a new object instead of the database's own primary-key # generator. # Instead of raising an exception, you can also choose to # ignore the primary key set by the client by silently doing # obj.id = None in order to have the database assign the # primary key the traditional way. raise ResourceNotFoundError('%s.id=%d' % (T_name, obj.id)) else: ctx.udc.session.merge(obj) return obj.id @rpc(M(UnsignedInteger32), _in_message_name='del_%s' % T_name, _in_variable_names={'obj_id': '%s_id' % T_name}) def del_(ctx, obj_id): count = ctx.udc.session.query(T).filter_by(id=obj_id).count() if count == 0: raise ResourceNotFoundError(obj_id) ctx.udc.session.query(T).filter_by(id=obj_id).delete() @rpc(_returns=Iterable(T), _in_message_name='get_all_%s' % T_name) def get_all(ctx): return ctx.udc.session.query(T) return CrudService class UserDefinedContext(object): def __init__(self): self.session = Session() def _on_method_call(ctx): ctx.udc = UserDefinedContext() def _on_method_return_object(ctx): ctx.udc.session.commit() def _on_method_context_closed(ctx): if ctx.udc is not None: ctx.udc.session.close() user_service = TCrudService(User, 'user') application = Application( [user_service], tns='spyne.examples.sql_crud', in_protocol=HttpRpc(validator='soft'), out_protocol=YamlDocument() ) application.event_manager.add_listener('method_call', _on_method_call) application.event_manager.add_listener('method_return_object', _on_method_return_object) application.event_manager.add_listener("method_context_closed", _on_method_context_closed) if __name__ == '__main__': from wsgiref.simple_server import make_server wsgi_app = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_app) TableModel.Attributes.sqla_metadata.create_all() logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/sql/sql_crud_simple.py000077500000000000000000000123341417664205300222620ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) from spyne import Application, rpc, Mandatory as M, Unicode, Array, Iterable, \ TTableModel, Service, ResourceNotFoundError, UnsignedInteger32 from spyne.protocol.http import HttpRpc from spyne.protocol.yaml import YamlDocument from spyne.util import memoize from spyne.server.wsgi import WsgiApplication from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker db = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=db) TableModel = TTableModel() TableModel.Attributes.sqla_metadata.bind = db class Permission(TableModel): __tablename__ = 'permission' __namespace__ = 'spyne.examples.sql_crud' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(primary_key=True) application = Unicode(256) operation = Unicode(256) class User(TableModel): __tablename__ = 'user' __namespace__ = 'spyne.examples.sql_crud' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(primary_key=True) name = Unicode(256) first_name = Unicode(256) last_name = Unicode(256) permissions = Array(Permission, store_as='table') @memoize def TCrudService(T, T_name): class CrudService(Service): @rpc(M(UnsignedInteger32, _returns=T)) def get(ctx, obj_id): return ctx.udc.session.query(T).filter_by(id=obj_id).one() @rpc(T, _returns=UnsignedInteger32) def put(ctx, obj): if obj.id is None: ctx.udc.session.add(obj) ctx.udc.session.flush() else: if ctx.udc.session.query(T).get(obj.id) is None: raise ResourceNotFoundError('%s.id=%d' % (T_name, obj.id)) else: ctx.udc.session.merge(obj) return obj.id @rpc(M(UnsignedInteger32)) def del_(ctx, obj_id): count = ctx.udc.session.query(T).filter_by(id=obj_id).count() if count == 0: raise ResourceNotFoundError(obj_id) ctx.udc.session.query(T).filter_by(id=obj_id).delete() @rpc(_returns=Iterable(T)) def get_all(ctx): return ctx.udc.session.query(T) return CrudService class UserDefinedContext(object): def __init__(self): self.session = Session() def _on_method_call(ctx): ctx.udc = UserDefinedContext() def _on_method_return_object(ctx): ctx.udc.session.commit() def _on_method_context_closed(ctx): if ctx.udc is not None: ctx.udc.session.close() user_service = TCrudService(User, 'user') application = Application( [user_service], tns='spyne.examples.sql_crud', in_protocol=HttpRpc(validator='soft'), out_protocol=YamlDocument() ) application.event_manager.add_listener('method_call', _on_method_call) application.event_manager.add_listener('method_return_object', _on_method_return_object) application.event_manager.add_listener("method_context_closed", _on_method_context_closed) if __name__ == '__main__': from wsgiref.simple_server import make_server wsgi_app = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_app) TableModel.Attributes.sqla_metadata.create_all() logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/template/000077500000000000000000000000001417664205300175315ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/.gitignore000066400000000000000000000000131417664205300215130ustar00rootroot00000000000000*.egg-info spyne-spyne-2.14.0/examples/template/README.rst000066400000000000000000000025221417664205300212210ustar00rootroot00000000000000 About ===== Spyne projects do not have to adhere to any specific file layout. However, the file layout in this example project seems to be quite tidy and comfortable to work with as most of the boilerplate for a propery Python project is already here. To start hacking on this project: #. Copy the template directory to a different folder. #. Do: :: find -name "*.py" | xargs sed -i s/template/your_project/g mv template your_project #. Tweak the requirements in setup.py according to the protocols and transports you choose to work with. You can look at Spyne's own README to see which protocol or transport requires which package. #. Tweak the database credentials and exception handling. #. Hack away! Installation ============ 1. Bootstrap distribute if needed: :: wget http://python-distribute.org/distribute_setup.py (yes, wget, not ``curl | python`` because setup script tries to import it) 2. Run: :: python setup.py develop --user The template_daemon executable is now installed at $HOME/.local/bin. You may need to add that path to your $PATH. Usage ===== 1. Run: :: template_deamon 2. In a separate console, run: :: curl "http://localhost:8000/put_user?user_user_name=jack&user_full_name=jack%20brown&user_email=jack@spyne.io" curl "http://localhost:8000/get_all_user" spyne-spyne-2.14.0/examples/template/setup.py000077500000000000000000000062261417664205300212540ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # try: from distribute_setup import use_setuptools use_setuptools() except ImportError: pass import os import re import sys from setuptools import setup from setuptools import find_packages from setuptools.command.test import test as TestCommand v = open(os.path.join(os.path.dirname(__file__), 'template', '__init__.py'), 'r') VERSION = re.match(r".*__version__ = '(.*?)'", v.read(), re.S).group(1) SHORT_DESC="""A Template project.""" LONG_DESC = """Yes, really, just a Template project.""" reqs = ['spyne>=2.11', 'SQLAlchemy>=0.8.0'] test_reqs = list(reqs) setup( name='template', packages=find_packages(), version=VERSION, description=SHORT_DESC, long_description=LONG_DESC, classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Operating System :: OS Independent', 'Natural Language :: English', 'Development Status :: 4 - Beta', ], keywords=['spyne'], author='Jack Brown', author_email='jack.brown@arskom.com.tr', maintainer='Jack Brown', maintainer_email='jack.brown@arskom.com.tr', url='http://example.com', license='Your Own', zip_safe=False, install_requires=reqs, entry_points={ 'console_scripts': [ 'template_daemon=template.main:main', ] }, tests_require = test_reqs, test_suite="template.test", ) spyne-spyne-2.14.0/examples/template/template/000077500000000000000000000000001417664205300213445ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/template/__init__.py000066400000000000000000000000271417664205300234540ustar00rootroot00000000000000 __version__ = '0.1.0' spyne-spyne-2.14.0/examples/template/template/application.py000066400000000000000000000062441417664205300242270ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logger = logging.getLogger(__name__) from spyne.application import Application from spyne.error import Fault from spyne.error import InternalError from spyne.error import ResourceNotFoundError from spyne.util.email import email_exception from sqlalchemy.orm.exc import NoResultFound from template.context import UserDefinedContext EXCEPTION_ADDRESS = "everybody@example.com" def _on_method_call(ctx): ctx.udc = UserDefinedContext() def _on_method_context_closed(ctx): if ctx.udc is not None: ctx.udc.session.commit() ctx.udc.session.close() class MyApplication(Application): def __init__(self, services, tns, name=None, in_protocol=None, out_protocol=None): super(MyApplication, self).__init__(services, tns, name, in_protocol, out_protocol) self.event_manager.add_listener('method_call', _on_method_call) self.event_manager.add_listener("method_context_closed", _on_method_context_closed) def call_wrapper(self, ctx): try: return ctx.service_class.call_wrapper(ctx) except NoResultFound: raise ResourceNotFoundError(ctx.in_object) except Fault, e: logger.error(e) raise except Exception, e: logger.exception(e) # This should not happen! Let the team know via email email_exception(EXCEPTION_ADDRESS) raise InternalError(e) spyne-spyne-2.14.0/examples/template/template/context.py000066400000000000000000000033441417664205300234060ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from template.db import Session class UserDefinedContext(object): def __init__(self): self.session = Session() spyne-spyne-2.14.0/examples/template/template/db.py000066400000000000000000000045121417664205300223050ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne.model.complex import TTableModel from spyne.model.primitive import UnsignedInteger32 from spyne.model.primitive import Unicode from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy.orm import sessionmaker db = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=db) TableModel = TTableModel(MetaData(bind=db)) class User(TableModel): __tablename__ = 'spyne_user' # This is only needed for sqlite __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(pk=True) user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+') full_name = Unicode(64, pattern='\w+( \w+)+') email = Unicode(64, pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}') spyne-spyne-2.14.0/examples/template/template/entity/000077500000000000000000000000001417664205300226605ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/template/entity/__init__.py000066400000000000000000000000001417664205300247570ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/template/entity/user.py000066400000000000000000000063621417664205300242170ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne.error import ResourceNotFoundError from spyne.service import Service from spyne.decorator import rpc from spyne.model.complex import Iterable from spyne.model.primitive import Mandatory from spyne.model.primitive import UnsignedInteger32 from template.db import User class UserManagerService(Service): @rpc(Mandatory.UnsignedInteger32, _returns=User) def get_user(ctx, user_id): return ctx.udc.session.query(User).filter_by(id=user_id).one() @rpc(User, _returns=UnsignedInteger32) def put_user(ctx, user): if user.id is None: ctx.udc.session.add(user) ctx.udc.session.flush() # so that we get the user.id value else: if ctx.udc.session.query(User).get(user.id) is None: # this is to prevent the client from setting the primary key # of a new object instead of the database's own primary-key # generator. # Instead of raising an exception, you can also choose to # ignore the primary key set by the client by silently doing # user.id = None raise ResourceNotFoundError('user.id=%d' % user.id) else: ctx.udc.session.merge(user) return user.id @rpc(Mandatory.UnsignedInteger32) def del_user(ctx, user_id): count = ctx.udc.session.query(User).filter_by(id=user_id).count() if count == 0: raise ResourceNotFoundError(user_id) ctx.udc.session.query(User).filter_by(id=user_id).delete() @rpc(_returns=Iterable(User)) def get_all_user(ctx): return ctx.udc.session.query(User) spyne-spyne-2.14.0/examples/template/template/main.py000066400000000000000000000053131417664205300226440ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logger = logging.getLogger(__name__) from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument from spyne.server.wsgi import WsgiApplication from template.application import MyApplication from template.db import TableModel from template.entity.user import UserManagerService from wsgiref.simple_server import make_server def main(): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) application = MyApplication([UserManagerService], 'spyne.examples.user_manager', in_protocol=HttpRpc(validator='soft'), out_protocol=JsonDocument(ignore_wrappers=1), ) wsgi_app = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_app) TableModel.Attributes.sqla_metadata.create_all(checkfirst=True) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") return server.serve_forever() spyne-spyne-2.14.0/examples/template/template/service.py000066400000000000000000000000701417664205300233530ustar00rootroot00000000000000 # you can put your own Service class definitions here. spyne-spyne-2.14.0/examples/template/template/test/000077500000000000000000000000001417664205300223235ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/template/test/__init__.py000066400000000000000000000000001417664205300244220ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/template/template/test/test_sample.py000066400000000000000000000034001417664205300252120ustar00rootroot00000000000000# encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import unittest class TestSample(unittest.TestCase): def test_sample(self): assert False if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/examples/testing/000077500000000000000000000000001417664205300173735ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/testing/helloworld_null.py000077500000000000000000000032371417664205300231620ustar00rootroot00000000000000 ''' This is a simple HelloWorld example showing how the NullServer works. The NullServer is meant to be used mainly for testing. ''' import logging logging.basicConfig(level=logging.INFO) from pprint import pprint from spyne.application import Application from spyne.protocol.soap import Soap11 from spyne.server.null import NullServer from spyne.decorator import rpc from spyne.service import Service from spyne.model.complex import Iterable from spyne.model.primitive import Integer from spyne.model.primitive import Unicode class HelloWorldService(Service): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): for i in range(times): yield u'Hello, %s' % name if __name__=='__main__': application = Application([HelloWorldService], 'spyne.examples.hello.soap', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(pretty_print=True), ) # disables context markers. set logging level to logging.INFO to enable # them. logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) print("With serialization") print("==================") print() null = NullServer(application, ostr=True) ret_stream = null.service.say_hello('Dave', 5) ret_string = ''.join(i.decode() for i in ret_stream) print(ret_string) print() print("Without serialization") print("=====================") print() null = NullServer(application, ostr=False) ret = null.service.say_hello('Dave', 5) # because the return value is a generator, we need to iterate over it to # see the actual return values. pprint(list(ret)) spyne-spyne-2.14.0/examples/tfb/000077500000000000000000000000001417664205300164715ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/tfb/README.md000066400000000000000000000013531417664205300177520ustar00rootroot00000000000000# [Spyne](http://spyne.io/) Benchmark Test This is the Python Spyne portion of a [benchmarking tests suite](../../) comparing a variety of frameworks. The latest version is at https://github.com/arskom/spyne/tree/master/examples/tfb All test implementations are located within ([app.py](app.py)) ## Description Spyne + SQLAlchemy ### Database PostgreSQL (psycopg2 on CPython) ### Server * gunicorn+wsgi on CPython ## Test URLs ### JSON Encoding http://localhost:8080/json ### Single Row Random Query With ORM: http://localhost:8080/dbs Without ORM (raw): http://localhost:8080/dbsraw ### Variable Row Query Test With ORM: http://localhost:8080/db?queries=2 Without ORM (raw): http://localhost:8080/dbraw?queries=2 spyne-spyne-2.14.0/examples/tfb/app.py000077500000000000000000000202231417664205300176250ustar00rootroot00000000000000#!/usr/bin/env python import sys import spyne.const spyne.const.MIN_GC_INTERVAL = float('inf') from lxml import html from random import randint, shuffle, choice from contextlib import closing from email.utils import formatdate from neurons import TableModel, Application from neurons.daemon import ServiceDefinition, HttpServer, StaticFileServer from neurons.daemon.main import Bootstrapper from spyne import Integer32, Unicode, rpc, ServiceBase, Integer, Array, Any from spyne.protocol.html import HtmlCloth from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument from spyne.server.wsgi import WsgiApplication if sys.version_info[0] == 3: xrange = range _is_pypy = hasattr(sys, 'pypy_version_info') DBDRIVER = 'postgresql+psycopg2cffi' if _is_pypy else 'postgresql+psycopg2' DBHOST = 'tfb-database' # models class DbSessionManager(object): def __init__(self, config): self.session = config.get_main_store().Session() def close(self, with_err): self.session.close() class DbConnectionManager(object): def __init__(self, config): self.conn = config.get_main_store().engine.connect() def close(self, with_err): self.conn.close() class World(TableModel): __tablename__ = "world" _type_info = [ ('id', Integer32(primary_key=True)), ('randomNumber', Integer32(sqla_column_args=dict(name='randomnumber'))), ] T_INDEX = html.fromstring(open('cloths/index.html', 'rb').read()) class Fortune(TableModel): __tablename__ = "fortune" id = Integer32(primary_key=True) message = Unicode outprot_plain = HttpRpc(mime_type='text/plain') class TfbSimpleService(ServiceBase): @rpc(_returns=Any) def json(ctx): ctx.transport.add_header('Date', formatdate(usegmt=True)) return dict(message=u'Hello, World!') @rpc(_returns=Any) def plaintext(ctx): """Test 6: Plaintext""" ctx.out_protocol = outprot_plain return b'Hello, World!' def _force_int(v): try: return min(500, max(int(v), 1)) except: return 1 NumQueriesType = Any(sanitizer=_force_int) class TfbOrmService(ServiceBase): @rpc(_returns=World) def db(ctx): retval = ctx.udc.session.query(World).get(randint(1, 10000)) return retval @rpc(NumQueriesType, _returns=Array(World)) def dbs(ctx, queries): if queries is None: queries = 1 q = ctx.udc.session.query(World) return [q.get(randint(1, 10000)) for _ in xrange(queries)] @rpc(_returns=Array(Fortune, html_cloth=T_INDEX), _body_style='out_bare') def fortunes(ctx): # This is normally specified at the application level as it's a good # practice to group rpc endpoints with similar return types under the # same url fragment. eg. https://example.com/api/json ctx.out_protocol = HtmlCloth() ctx.outprot_ctx = ctx.out_protocol.get_context(ctx, ctx.transport) fortunes = ctx.udc.session.query(Fortune).all() fortunes.append( Fortune(id=0, message=u"Additional fortune added at request time.") ) fortunes.sort(key=lambda x: x.message) return fortunes @rpc(NumQueriesType, _returns=Array(World)) def updates(ctx, queries): """Test 5: Database Updates""" if queries is None: queries = 1 retval = [] q = ctx.udc.session.query(World) for id in (randint(1, 10000) for _ in xrange(queries)): world = q.get(id) world.randomNumber = randint(1, 10000) retval.append(world) ctx.udc.session.commit() return retval class TfbRawService(ServiceBase): @rpc(_returns=World) def dbraw(ctx): conn = ctx.udc.conn wid = randint(1, 10000) return conn.execute( "SELECT id, randomNumber FROM world WHERE id = %s", wid) \ .fetchone() # returning both Any+dict or ObjectMarker+ListOfLists works @rpc(NumQueriesType, _returns=Any) def dbsraw(ctx, queries): if queries is None: queries = 1 retval = [] conn = ctx.udc.conn for i in xrange(queries): wid = randint(1, 10000) result = conn.execute( "SELECT id, randomNumber FROM world WHERE id = %s", wid) \ .fetchone() retval.append(dict(id=result[0], randomNumber=result[1])) return retval @rpc(_returns=Array(Fortune, html_cloth=T_INDEX), _body_style='out_bare') def fortunesraw(ctx): # This is normally specified at the application level as it's a good # practice to group rpc endpoints with similar return types under the # same url fragment. eg. https://example.com/api/json ctx.out_protocol = HtmlCloth() ctx.outprot_ctx = ctx.out_protocol.get_context(ctx, ctx.transport) res = ctx.udc.conn.execute("SELECT id, message FROM fortune") fortunes = res.fetchall() fortunes.append(Fortune( id=0, message=u"Additional fortune added at request time." )) fortunes.sort(key=lambda x: x.message) return fortunes @rpc(NumQueriesType, _returns=Any) def updatesraw(ctx, queries): """Test 5: Database Updates""" if queries is None: queries = 1 conn = ctx.udc.conn ids = [randint(1, 10000) for _ in xrange(queries)] retval = [] for i in ids: wid, rn = conn.execute( "SELECT id, randomNumber FROM world WHERE id=%s", i) \ .fetchone() rn = randint(1, 10000) retval.append(dict(id=wid, randomNumber=rn)) conn.execute("UPDATE World SET randomNumber=%s WHERE id=%s", rn, wid) return retval def _on_method_call_db_sess(ctx): ctx.transport.add_header('Date', formatdate(usegmt=True)) ctx.udc = DbSessionManager(ctx.app.config) def _on_method_call_db_conn(ctx): ctx.transport.add_header('Date', formatdate(usegmt=True)) ctx.udc = DbConnectionManager(ctx.app.config) TfbRawService.event_manager.add_listener("method_call", _on_method_call_db_conn) TfbOrmService.event_manager.add_listener("method_call", _on_method_call_db_sess) def init_app(config): subconfig = config.services['root'] app = Application( [TfbOrmService, TfbRawService, TfbSimpleService], tns='http://techempower.com/benchmarks/Python/Spyne', in_protocol=HttpRpc(), out_protocol=JsonDocument(), config=config, ) if subconfig.subapps is None: subconfig.subapps = {} subconfig.subapps.update({'': app}) return subconfig.gen_site() def init(config): return { 'root': ServiceDefinition( init=init_app, default=HttpServer( type='tcp4', host='127.0.0.1', port=8080, ), ), } def parse_config(argv): from neurons.daemon.main import boot retcode, config = boot('tfb', argv, init, bootstrapper=TfbBootstrap) if retcode is not None: sys.exit(retcode) return config def gen_wsgi_app(): config = parse_config([]) app = config.services['root'].subapps[''].app return WsgiApplication(app) words = 'some random words for you and me somebody else if then the'.split() class TfbBootstrap(Bootstrapper): # noinspection PyUnresolvedReferences def after_tables(self, config): print("Generating data...") with closing(config.get_main_store().Session()) as session: ints = list(range(10000)) shuffle(ints) for _ in range(10000): session.add(World(randomNumber=ints.pop())) for _ in range(100): session.add(Fortune( message=' '.join([choice(words) for _ in range(randint(3, 10))]) )) session.commit() if __name__ == '__main__': parse_config(sys.argv) else: application = gen_wsgi_app() spyne-spyne-2.14.0/examples/tfb/benchmark_config.json000066400000000000000000000034731417664205300226520ustar00rootroot00000000000000{ "framework": "spyne", "tests": [{ "default": { "json_url": "/json", "db_url": "/db", "query_url": "/dbs?queries=", "fortune_url": "/fortunes", "update_url": "/updates?queries=", "plaintext_url": "/plaintext", "port": 8080, "approach": "Realistic", "classification": "Micro", "database": "postgres", "framework": "spyne", "language": "Python", "flavor": "Python3", "orm": "Full", "platform": "Spyne", "webserver": "None", "os": "Linux", "database_os": "Linux", "display_name": "Spyne", "notes": "", "versus": "wsgi" }, "raw": { "db_url": "/dbraw", "query_url": "/dbsraw?queries=", "fortune_url": "/fortunesraw", "update_url": "/updatesraw?queries=", "port": 8080, "approach": "Realistic", "classification": "Micro", "database": "postgres", "framework": "spyne", "language": "Python", "flavor": "Python3", "orm": "Raw", "platform": "Spyne", "webserver": "None", "os": "Linux", "database_os": "Linux", "display_name": "Spyne-raw", "notes": "", "versus": "wsgi" }, "nginx-uwsgi": { "json_url": "/json", "db_url": "/db", "query_url": "/dbs?queries=", "fortune_url": "/fortunes", "update_url": "/updates?queries=", "plaintext_url": "/plaintext", "port": 8080, "approach": "Realistic", "classification": "Micro", "database": "postgres", "framework": "spyne", "language": "Python", "flavor": "Python3", "orm": "Full", "platform": "None", "webserver": "nginx", "os": "Linux", "database_os": "Linux", "display_name": "Spyne", "notes": "", "versus": "wsgi" } }] } spyne-spyne-2.14.0/examples/tfb/cloths/000077500000000000000000000000001417664205300177655ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/tfb/cloths/index.html000066400000000000000000000003511417664205300217610ustar00rootroot00000000000000 Fortunes
id message
spyne-spyne-2.14.0/examples/tfb/gen_benchmark_config.py000077500000000000000000000052601417664205300231610ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import json from spyne import AnyUri, Unicode, ComplexModel, M, UnsignedInteger16, Array from spyne.protocol.json import JsonDocument from spyne.util.dictdoc import get_object_as_dict class BenchmarkConfigElement(ComplexModel): # exclude this from the output document key = Unicode(pa={JsonDocument: dict(exc=True)}) display_name = M(Unicode) notes = Unicode versus = Unicode db_url = AnyUri json_url = AnyUri query_url = AnyUri fortune_url = AnyUri update_url = AnyUri plaintext_url = AnyUri port = M(UnsignedInteger16(default=8080)) approach = M(Unicode(values=['Realistic', 'Stripped'], default='Realistic')) classification = M(Unicode(values=['Micro', 'Fullstack', 'Platform'], default='Micro')) database = M(Unicode(values=['none', 'mongodb', 'postgres', 'mysql'], default='none')) orm = M(Unicode(values=['Full', 'Micro', 'None', 'Raw'])) framework = M(Unicode) language = M(Unicode) flavor = M(Unicode) platform = M(Unicode) webserver = M(Unicode) os = M(Unicode(default='Linux')) database_os = M(Unicode(default='Linux')) class BenchmarkConfig(ComplexModel): framework = M(Unicode) tests = Array(BenchmarkConfigElement, wrapped=False) gen_raw_test = lambda: BenchmarkConfigElement( display_name="Spyne RAW", db_url="/dbsraw", query_url="/dbraw?queries=", fortune_url="/fortunesraw", update_url="/raw-updates?queries=", orm='Raw', ) gen_normal_test = lambda: BenchmarkConfigElement( display_name="Spyne ORM", db_url="/dbs", query_url="/db?queries=", fortune_url="/fortunes", update_url="/updatesraw?queries=", orm='Full', ) def add_common(bc): bc.port = 8080 bc.approach = "Realistic" bc.classification = "Micro" bc.database = "postgres" bc.framework = "spyne" bc.language = "Python" bc.platform = "Spyne" bc.webserver = "None" bc.os = "Linux" bc.database_os = "Linux" bc.versus = "wsgi" bc.plaintext_url = "/plaintext" return bc config = BenchmarkConfig(framework='spyne', tests=[]) keys = iter(['default', 'raw', 'py3orm', 'py3raw']) for flav in ['CPython', 'Python3']: bc = add_common(gen_normal_test()) bc.flavor = flav bc.key = next(keys) config.tests.append(bc) bc = add_common(gen_raw_test()) bc.flavor = flav bc.key = next(keys) config.tests.append(bc) data = get_object_as_dict(config, complex_as=dict) data['tests'] = [{d['key']: d} for d in data['tests']] data = json.dumps(data, indent=2, sort_keys=True, separators=(',', ': ')) open('benchmark_config.json', 'wb').write(data) print(data) spyne-spyne-2.14.0/examples/tfb/gunicorn_conf.py000066400000000000000000000002461417664205300216760ustar00rootroot00000000000000import multiprocessing import os import sys workers = multiprocessing.cpu_count() * 3 bind = "0.0.0.0:8080" keepalive = 120 errorlog = '-' pidfile = 'gunicorn.pid' spyne-spyne-2.14.0/examples/tfb/nginx.conf000066400000000000000000000032061417664205300204640ustar00rootroot00000000000000# This file is based on /usr/local/nginx/conf/nginx.conf.default. # One worker process per core error_log stderr error; events { # This needed to be increased because the nginx error log said so. # http://nginx.org/en/docs/ngx_core_module.html#worker_connections worker_connections 65535; multi_accept on; } http { default_type application/octet-stream; client_body_temp_path /tmp; # turn off request logging for performance access_log off; # I think these only options affect static file serving sendfile on; tcp_nopush on; # Allow many HTTP Keep-Alive requests in a single TCP connection before # closing it (the default is 100). This will minimize the total number # of TCP connections opened/closed. The problem is that this may cause # some worker processes to be handling too connections relative to the # other workers based on an initial imbalance, so this is disabled for # now. # keepalive_requests 1000; #keepalive_timeout 0; keepalive_timeout 65; server { # For information on deferred, see: # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen # http://www.techrepublic.com/article/take-advantage-of-tcp-ip-options-to-optimize-data-transmission/ # The backlog argument to listen() is set to match net.ipv4.tcp_max_syn_backlog and net.core.somaxconn listen 8080 default_server deferred reuseport backlog=65535; server_name localhost; location / { uwsgi_pass unix:/var/tmp/uwsgi.sock; include /usr/local/nginx/conf/uwsgi_params; } } } spyne-spyne-2.14.0/examples/tfb/requirements.txt000066400000000000000000000006761417664205300217660ustar00rootroot00000000000000attrs==18.2.0 Automat==0.7.0 colorama==0.4.1 constantly==15.1.0 greenlet==0.4.15 gunicorn==19.9.0 hyperlink==18.0.0 idna==2.8 incremental==17.5.0 lxml==4.3.1 meinheld==0.6.1 msgpack-python==0.5.6 neurons==0.8.4 ply==3.11 psycopg2==2.7.7 pycrypto==2.6.1 PyHamcrest==1.9.0 pytz==2018.9 PyYAML==4.2b4 six==1.12.0 slimit==0.8.1 spyne==2.13.9a0 SQLAlchemy==1.2.17 Twisted==18.9.0 txpostgres==1.6.0 Werkzeug==0.14.1 zope.interface==4.6.0 uwsgi==2.0.18 spyne-spyne-2.14.0/examples/tfb/spyne-nginx-uwsgi.dockerfile000066400000000000000000000011441417664205300241350ustar00rootroot00000000000000FROM python:3.6.6-stretch RUN curl -s http://nginx.org/keys/nginx_signing.key | apt-key add - RUN echo "deb http://nginx.org/packages/debian/ stretch nginx" >> /etc/apt/sources.list RUN echo "deb-src http://nginx.org/packages/debian/ stretch nginx" >> /etc/apt/sources.list RUN apt update -yqq && apt install -yqq nginx ADD ./ /spyne WORKDIR /spyne RUN pip3 install -r /spyne/requirements.txt RUN sed -i 's|include .*/conf/uwsgi_params;|include /etc/nginx/uwsgi_params;|g' /spyne/nginx.conf CMD nginx -c /spyne/nginx.conf && uwsgi --ini /spyne/uwsgi.ini --processes $(($(nproc)*3)) --wsgi app:application spyne-spyne-2.14.0/examples/tfb/spyne-raw.dockerfile000066400000000000000000000002301417664205300224420ustar00rootroot00000000000000FROM python:3.6.6-stretch ADD ./ /spyne WORKDIR /spyne RUN pip3 install -r /spyne/requirements.txt CMD gunicorn app:application -c gunicorn_conf.py spyne-spyne-2.14.0/examples/tfb/spyne.dockerfile000066400000000000000000000002301417664205300216530ustar00rootroot00000000000000FROM python:3.6.6-stretch ADD ./ /spyne WORKDIR /spyne RUN pip3 install -r /spyne/requirements.txt CMD gunicorn app:application -c gunicorn_conf.py spyne-spyne-2.14.0/examples/tfb/tfb.yaml000066400000000000000000000025671417664205300201420ustar00rootroot00000000000000ServiceDaemon: alert_dests: [] autoreload: false daemonize: false debug: true debug_reactor: false file_version: 2 log_cloth: false log_dbconn: false log_interface: false log_model: false log_orm: false log_protocol: false log_queries: false log_results: false log_rss: false log_sqlalchemy: false logger_dest_rotation_compression: gzip logger_dest_rotation_period: WEEKLY loggers: - Logger: level: ERROR path: . main_store: sql_main name: tfb services: - HttpServer: backlog: 50 disabled: true host: 127.0.0.1 name: root port: 8080 subapps: - HttpApplication: url: '' type: tcp4 stores: - RelationalStore: async_pool: true backend: sqlalchemy conn_str: postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world echo_pool: false max_overflow: 3 name: sql_main pool_pre_ping: true pool_recycle: 3600 pool_size: 10 pool_timeout: 30 pool_use_lifo: false sync_case_sensitive: false sync_pool: true sync_pool_type: QueuePool uuid: fef52ff8-3103-11e9-b0e1-5453edabe249 spyne-spyne-2.14.0/examples/tfb/uwsgi.ini000066400000000000000000000014341417664205300203320ustar00rootroot00000000000000[uwsgi] master ; Increase listen queue used for nginx connecting to uWSGI. This matches ; net.ipv4.tcp_max_syn_backlog and net.core.somaxconn. listen = 16384 ; for performance disable-logging ; use UNIX sockets instead of TCP loopback for performance socket = /var/tmp/uwsgi.sock ; allow nginx to access the UNIX socket chmod-socket = 666 ; Avoid thundering herd problem http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html . ; This is currently disabled because when I tried it with flask, it caused a ; 20% performance hit. The CPU cores could not be saturated with thunder-lock. ; I'm not yet sure the full story, so this is presently disabled. Also, ; disabling this caused bottle to get ~13% faster. ;thunder-lock ; used by uwsgi_stop.ini pidfile = /var/tmp/uwsgi.pid spyne-spyne-2.14.0/examples/twisted/000077500000000000000000000000001417664205300174015ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/twisted/codegen.py000077500000000000000000000015511417664205300213640ustar00rootroot00000000000000#!/usr/bin/env python import logging logging.basicConfig(level=logging.DEBUG) from spyne import Application, rpc, ServiceBase, \ Integer, Unicode from spyne import Iterable from spyne.protocol.soap import Soap11 from spyne.server.twisted import TwistedWebResource from twisted.internet import reactor from twisted.web.server import Site class HelloWorldService(ServiceBase): @rpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(ctx, name, times): for i in range(times): yield 'Hello, %s' % name application = Application([HelloWorldService], tns='spyne.examples.hello', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) if __name__ == '__main__': resource = TwistedWebResource(application) site = Site(resource) reactor.listenTCP(8000, site, interface='0.0.0.0') reactor.run() spyne-spyne-2.14.0/examples/twisted/resource.py000077500000000000000000000117671417664205300216210ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """This is a blocking example running in a single-process twisted setup. In this example, user code runs directly in the reactor loop. So unless your code fully adheres to the asynchronous programming principles, you can block the reactor loop. :: $ time curl -s "http://localhost:9757/block?seconds=10" > /dev/null & \ time curl -s "http://localhost:9757/block?seconds=10" > /dev/null & [1] 27559 [2] 27560 real 0m10.026s user 0m0.005s sys 0m0.008s real 0m20.045s user 0m0.009s sys 0m0.005s If you call sleep, it sleeps by returning a deferred: :: $ time curl -s "http://localhost:9757/sleep?seconds=10" > /dev/null & \ time curl -s "http://localhost:9757/sleep?seconds=10" > /dev/null & [1] 27778 [2] 27779 real 0m10.012s user 0m0.000s sys 0m0.000s real 0m10.013s user 0m0.000s sys 0m0.000s """ import sys import time import logging from twisted.internet import reactor from twisted.web.server import Site from twisted.internet.task import deferLater from twisted.python import log from spyne import Unicode, Integer, Double, ByteArray, Iterable, rpc, \ Service, Application from spyne.server.twisted import TwistedWebResource from spyne.protocol.http import HttpRpc HOST = '0.0.0.0' PORT = 9758 class SomeService(Service): @rpc(Integer, _returns=Integer) def block(ctx, seconds): """Blocks the current thread for given number of seconds.""" time.sleep(seconds) return seconds class SomeNonBlockingService(Service): @rpc(Integer, _returns=Unicode) def sleep(ctx, seconds): """Waits without blocking reactor for given number of seconds by returning a deferred.""" def _cb(): return "slept for %r seconds" % seconds return deferLater(reactor, seconds, _cb) @rpc(Unicode, Double, Double, _returns=ByteArray) def say_hello_with_sleep(ctx, name, times, seconds): """Sends multiple hello messages by waiting given number of seconds inbetween.""" times = [times] # Workaround for Python 2's lacking of nonlocal def _cb(response): if times[0] > 0: msg = u"Hello %s, sleeping for %f seconds for " \ u"%d more time(s)." % (name, seconds, times[0]) response.append(msg.encode('utf8')) response.append(b'\n') logging.debug(msg) times[0] -= 1 return deferLater(reactor, seconds, _cb, response) return Iterable.Push(_cb) def initialize(services, tns='spyne.examples.twisted.resource'): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) return Application(services, 'spyne.examples.twisted.hello', in_protocol=HttpRpc(), out_protocol=HttpRpc()) if __name__=='__main__': application = initialize([SomeService, SomeNonBlockingService]) resource = TwistedWebResource(application) site = Site(resource) reactor.listenTCP(PORT, site, interface=HOST) logging.info("listening on: %s:%d" % (HOST,PORT)) logging.info('wsdl is at: http://%s:%d/?wsdl' % (HOST, PORT)) sys.exit(reactor.run()) spyne-spyne-2.14.0/examples/twisted/resource_push.py000077500000000000000000000101641417664205300226460ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from __future__ import print_function """This shows the async push capabilities of Spyne using twisted in async mode. In this example, user code runs directly in the reactor loop. Data is pushed to the output slowly without blocking other requests. """ import logging import sys from twisted.internet import reactor from twisted.internet.task import deferLater from twisted.web.server import Site from spyne import Application, rpc, Service, Iterable, Unicode, \ UnsignedInteger from spyne.protocol.http import HttpRpc from spyne.protocol.html import HtmlColumnTable from spyne.server.twisted import TwistedWebResource HOST = '127.0.0.1' PORT = 8000 class HelloWorldService(Service): @rpc(Unicode(default='World'), UnsignedInteger(default=5), _returns=Iterable(Unicode)) def say_hello(ctx, name, times): # workaround for Python2's lacking of nonlocal times = [times] def _cb(push): # This callback is called immediately after the function returns. if times[0] > 0: times[0] -= 1 data = u'Hello, %s' % name print(data) # The object passed to the append() method is immediately # serialized to bytes and pushed to the response stream's # file-like object. push.append(data) # When a push-callback returns anything other than a deferred, # the response gets closed. return deferLater(reactor, 1, _cb, push) # This is Spyne's way of returning NOT_DONE_YET return Iterable.Push(_cb) @rpc(Unicode(default='World'), _returns=Iterable(Unicode)) def say_hello_forever(ctx, name): def _cb(push): push.append(u'Hello, %s' % name) return deferLater(reactor, 0.1, _cb, push) return Iterable.Push(_cb) if __name__=='__main__': application = Application([HelloWorldService], 'spyne.examples.twisted.resource_push', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(), ) resource = TwistedWebResource(application) site = Site(resource) reactor.listenTCP(PORT, site, interface=HOST) logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening on: %s:%d" % (HOST,PORT)) logging.info('wsdl is at: http://%s:%d/?wsdl' % (HOST, PORT)) sys.exit(reactor.run()) spyne-spyne-2.14.0/examples/twisted/wsgi.py000077500000000000000000000072261417664205300207360ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a blocking example running in a multi-threaded twisted setup. This is a way of weakly integrating with the twisted framework -- every request still runs in its own thread. This way, you can still use other features of twisted and not have to rewrite your otherwise synchronous code. $ time curl -s "http://localhost:9757/block?seconds=10" > /dev/null & \ time curl -s "http://localhost:9757/block?seconds=10" > /dev/null & [1] 27537 [2] 27538 real 0m10.031s user 0m0.008s sys 0m0.007s real 0m10.038s user 0m0.006s sys 0m0.006s """ import sys import time import logging from twisted.internet import reactor from twisted.web.server import Site from twisted.web.wsgi import WSGIResource from twisted.python import log from spyne import Application, rpc, Service, Integer from spyne.server.wsgi import WsgiApplication from spyne.protocol.http import HttpRpc HOST = '0.0.0.0' PORT = 9757 class SomeService(Service): @rpc(Integer, _returns=Integer) def block(ctx, seconds): """Blocks the current thread for given number of seconds.""" time.sleep(seconds) return seconds def initialize(services, tns='spyne.examples.twisted.resource'): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) application = Application(services, 'spyne.examples.twisted.hello', in_protocol=HttpRpc(), out_protocol=HttpRpc()) return application if __name__ == '__main__': application = initialize([SomeService]) wsgi_application = WsgiApplication(application) resource = WSGIResource(reactor, reactor, wsgi_application) site = Site(resource) reactor.listenTCP(PORT, site, interface=HOST) logging.info('listening on: %s:%d' % (HOST,PORT)) logging.info('wsdl is at: http://%s:%d/?wsdl' % (HOST, PORT)) sys.exit(reactor.run()) spyne-spyne-2.14.0/examples/user_manager/000077500000000000000000000000001417664205300203665ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/user_manager/server_basic.py000077500000000000000000000111111417664205300234050ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) from spyne.application import Application from spyne.decorator import rpc from spyne.error import ResourceNotFoundError from spyne.protocol.soap import Soap11 from spyne.model.primitive import Mandatory from spyne.model.primitive import Unicode from spyne.model.primitive import UnsignedInteger32 from spyne.model.complex import Array from spyne.model.complex import Iterable from spyne.model.complex import ComplexModel from spyne.server.wsgi import WsgiApplication from spyne.service import Service _user_database = {} _user_id_seq = 0 class Permission(ComplexModel): __namespace__ = 'spyne.examples.user_manager' id = UnsignedInteger32 application = Unicode(values=('usermgr', 'accountmgr')) operation = Unicode(values=('read', 'modify', 'delete')) class User(ComplexModel): __namespace__ = 'spyne.examples.user_manager' id = UnsignedInteger32 user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+') full_name = Unicode(64, pattern='\w+( \w+)+') email = Unicode(pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[A-Z]{2,4}') permissions = Array(Permission) class UserManagerService(Service): @rpc(User.customize(min_occurs=1, nullable=False), _returns=UnsignedInteger32) def put_user(ctx, user): if user.id is None: user.id = ctx.udc.get_next_user_id() ctx.udc.users[user.id] = user return user.id @rpc(Mandatory.UnsignedInteger32, _returns=User) def get_user(ctx, user_id): return ctx.udc.users[user_id] @rpc(Mandatory.UnsignedInteger32) def del_user(ctx, user_id): del ctx.udc.users[user_id] @rpc(_returns=Iterable(User)) def get_all_users(ctx): return ctx.udc.users.itervalues() class UserDefinedContext(object): def __init__(self): self.users = _user_database @staticmethod def get_next_user_id(): global _user_id_seq _user_id_seq += 1 return _user_id_seq class MyApplication(Application): def call_wrapper(self, ctx): try: return super(MyApplication, self).call_wrapper(ctx) except KeyError: raise ResourceNotFoundError(ctx.in_object) def _on_method_call(ctx): ctx.udc = UserDefinedContext() application = MyApplication([UserManagerService], 'spyne.examples.user_manager', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) application.event_manager.add_listener('method_call', _on_method_call) def main(): from wsgiref.simple_server import make_server server = make_server('127.0.0.1', 8000, WsgiApplication(application)) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") return server.serve_forever() if __name__=='__main__': import sys; sys.exit(main()) spyne-spyne-2.14.0/examples/user_manager/server_sqlalchemy.py000077500000000000000000000142551417664205300245020ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.getLogger('sqlalchemy.engine.base.Engine').setLevel(logging.DEBUG) from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.exc import NoResultFound from spyne import Application, rpc, ResourceNotFoundError, M, \ Unicode, InternalError, Fault, Array, Iterable, UnsignedInteger32, \ Service, TTableModel from spyne.server.wsgi import WsgiApplication from spyne.protocol.soap import Soap11 db = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=db) TableModel = TTableModel() TableModel.Attributes.sqla_metadata.bind = db class Permission(TableModel): __tablename__ = 'permission' __namespace__ = 'spyne.examples.user_manager' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(pk=True) application = Unicode(values=('usermgr', 'accountmgr')) operation = Unicode(values=('read', 'modify', 'delete')) class User(TableModel): __tablename__ = 'user' __namespace__ = 'spyne.examples.user_manager' __table_args__ = {"sqlite_autoincrement": True} id = UnsignedInteger32(pk=True) email = Unicode(64, pattern=r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}') user_name = Unicode(32, min_len=4, pattern='[a-z0-9.]+') full_name = Unicode(64, pattern='\w+( \w+)+') permissions = Array(Permission).store_as('table') class UserManagerService(Service): @rpc(M(UnsignedInteger32), _returns=User) def get_user(ctx, user_id): return ctx.udc.session.query(User).filter_by(id=user_id).one() @rpc(User, _returns=UnsignedInteger32) def put_user(ctx, user): if user.id is None: ctx.udc.session.add(user) ctx.udc.session.flush() # so that we get the user.id value else: if ctx.udc.session.query(User).get(user.id) is None: # this is to prevent the client from setting the primary key # of a new object instead of the database's own primary-key # generator. # Instead of raising an exception, you can also choose to # ignore the primary key set by the client by silently doing # user.id = None raise ResourceNotFoundError('user.id=%d' % user.id) else: ctx.udc.session.merge(user) return user.id @rpc(M(UnsignedInteger32)) def del_user(ctx, user_id): count = ctx.udc.session.query(User).filter_by(id=user_id).count() if count == 0: raise ResourceNotFoundError(user_id) ctx.udc.session.query(User).filter_by(id=user_id).delete() @rpc(_returns=Iterable(User)) def get_all_user(ctx): return ctx.udc.session.query(User) class UserDefinedContext(object): def __init__(self): self.session = Session() def _on_method_call(ctx): ctx.udc = UserDefinedContext() def _on_method_context_closed(ctx): if ctx.udc is not None: ctx.udc.session.commit() ctx.udc.session.close() class MyApplication(Application): def __init__(self, services, tns, name=None, in_protocol=None, out_protocol=None): super(MyApplication, self).__init__(services, tns, name, in_protocol, out_protocol) self.event_manager.add_listener('method_call', _on_method_call) self.event_manager.add_listener("method_context_closed", _on_method_context_closed) def call_wrapper(self, ctx): try: return ctx.service_class.call_wrapper(ctx) except NoResultFound: raise ResourceNotFoundError(ctx.in_object) except Fault as e: logging.error(e) raise except Exception as e: logging.exception(e) raise InternalError(e) if __name__=='__main__': from wsgiref.simple_server import make_server application = MyApplication([UserManagerService], 'spyne.examples.user_manager', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(), ) wsgi_app = WsgiApplication(application) server = make_server('127.0.0.1', 8000, wsgi_app) TableModel.Attributes.sqla_metadata.create_all() logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server.serve_forever() spyne-spyne-2.14.0/examples/user_manager/spyne_client.py000077500000000000000000000045131417664205300234420ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from pprint import pprint from spyne.client.http import HttpClient from server_basic import application c = HttpClient('http://localhost:8000/', application) u = c.factory.create("User") u.user_name = 'dave' u.first_name = 'david' u.last_name = 'smith' u.email = 'david.smith@example.com' u.permissions = [] permission = c.factory.create("Permission") permission.application = 'table' permission.operation = 'write' u.permissions.append(permission) permission = c.factory.create("Permission") permission.application = 'table' permission.operation = 'read' u.permissions.append(permission) retval = c.service.add_user(u) print(retval) pprint(c.service.get_user(retval)) pprint(list(c.service.get_all_user())) spyne-spyne-2.14.0/examples/user_manager/suds_client.py000077500000000000000000000045171417664205300232660ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from suds import TypeNotFound from suds.client import Client has_permissions = True c = Client('http://localhost:8000?wsdl') u = c.factory.create("User") u.user_name = 'dave' u.full_name = 'david smith' u.email = 'dave@email.com' u.permissions = c.factory.create("PermissionArray") permission = c.factory.create("Permission") permission.application = 'usermgr' permission.operation = 'modify' u.permissions.Permission.append(permission) permission = c.factory.create("Permission") permission.application = 'accountmgr' permission.operation = 'read' u.permissions.Permission.append(permission) print(u) retval = c.service.put_user(u) print(retval) print(c.service.get_user(retval)) print(c.service.get_all_user()) spyne-spyne-2.14.0/examples/validation.py000077500000000000000000000050331417664205300204260ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """Use: curl http://localhost:9912/get_name_of_month?month=12 to use this service. """ host = '127.0.0.1' port = 8000 import logging from datetime import datetime from spyne import Integer, Unicode, rpc, Service class NameOfMonthService(Service): @rpc(Integer(ge=1, le=12), _returns=Unicode) def get_name_of_month(ctx, month): return datetime(2000, month, 1).strftime("%B") from spyne.application import Application from spyne.protocol.http import HttpRpc rest = Application([NameOfMonthService], tns='spyne.examples.multiprot', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc() ) from spyne.server.wsgi import WsgiApplication from wsgiref.simple_server import make_server server = make_server(host, port, WsgiApplication(rest)) logging.basicConfig(level=logging.DEBUG) logging.info("listening to http://%s:%d" % (host, port)) server.serve_forever() spyne-spyne-2.14.0/examples/xml/000077500000000000000000000000001417664205300165165ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/xml/Makefile000066400000000000000000000010411417664205300201520ustar00rootroot00000000000000all: wsse.xsd wsu.xsd ds.xsd xsd.xsd xml.xsd clean: -rm *.xsd wsse.xsd: wget "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" -O wsse.xsd wsu.xsd: wget "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" -O wsu.xsd ds.xsd: wget "https://raw.githubusercontent.com/arskom/pyubl/master/ubl/const/schema/common/xmldsig-core-schema.xsd" -O ds.xsd xsd.xsd: wget "https://www.w3.org/2001/XMLSchema.xsd" -O xsd.xsd xml.xsd: wget "http://www.w3.org/2001/xml.xsd" -O xml.xsd spyne-spyne-2.14.0/examples/xml/polymorphic_array.py000077500000000000000000000055671417664205300226530ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from __future__ import absolute_import import sys from spyne import ComplexModel, Unicode, Integer, Array, TTableModel, rpc, \ Application, Service from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.util.cherry import cherry_graft_and_start TableModel = TTableModel() class Vehicle(ComplexModel): _type_info = [ ('owner', Unicode), ] class Car(Vehicle): _type_info = [ ('color', Unicode), ('speed', Integer), ] class Bike(Vehicle): _type_info = [ ('size', Integer), ] class Garage(ComplexModel): _type_info = [ ('vehicles', Array(Vehicle)), ] class SomeService(Service): @rpc(_returns=Garage) def get_garage_dump(self): return Garage( vehicles=[ Car( color="blue", speed=100, owner="Simba" ), Bike( size=58, owner="Nala" ), ] ) application = Application([SomeService], 'tns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(polymorphic=True) ) sys.exit(cherry_graft_and_start(WsgiApplication(application))) spyne-spyne-2.14.0/examples/xml/polymorphic_roundtrip.py000066400000000000000000000030401417664205300235400ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import print_function, unicode_literals import sys from lxml import etree from spyne.interface.xml_schema.parser import hier_repr from spyne.util import six from spyne import ComplexModel, Unicode from spyne.util.xml import get_object_as_xml_polymorphic, \ get_xml_as_object_polymorphic # uncomment to see what's going on under the hood # import logging # logging.basicConfig(level=logging.DEBUG, # format='%(levelname)-7s %(module)12s:%(lineno)-4d | %(message)s') class B(ComplexModel): __namespace__ = 'some_ns' _type_info = { '_b': Unicode, } def __init__(self): super(B, self).__init__() self._b = "b" class C(B): __namespace__ = 'some_ns' _type_info = { '_c': Unicode, } def __init__(self): super(C, self).__init__() self._c = "c" class A(ComplexModel): __namespace__ = 'some_ns' _type_info = { '_a': Unicode, '_b': B, } def __init__(self, b=None): super(A, self).__init__() self._a = 'a' self._b = b a = A(b=C()) elt = get_object_as_xml_polymorphic(a, A) xml_string = etree.tostring(elt, pretty_print=True) if six.PY2: print(xml_string, end="") else: sys.stdout.buffer.write(xml_string) element_tree = etree.fromstring(xml_string) new_a = get_xml_as_object_polymorphic(elt, A) assert new_a._a == a._a, (a._a, new_a._a) assert new_a._b._b == a._b._b, (a._b._b, new_a._b._b) assert new_a._b._c == a._b._c, (a._b._c, new_a._b._c) print(hier_repr(new_a)) spyne-spyne-2.14.0/examples/xml/polymorphism.py000077500000000000000000000047711417664205300216460ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import sys from datetime import datetime from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.util.cherry import cherry_graft_and_start from spyne import DateTime, Unicode, Integer, ComplexModel, rpc, Application, \ Service class A(ComplexModel): i = Integer class B(A): s = Unicode class C(A): d = DateTime class SomeService(Service): @rpc(Unicode(values=['A', 'B', 'C']), _returns=A) def get_some_a(self, type_name): if type_name == 'A': return A(i=1) if type_name == 'B': return B(i=2, s='s') if type_name == 'C': return C(i=3, d=datetime.utcnow()) application = Application([SomeService], 'tns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11(polymorphic=True) ) sys.exit(cherry_graft_and_start(WsgiApplication(application))) spyne-spyne-2.14.0/examples/xml/schema.py000077500000000000000000000063101417664205300203330ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from lxml import etree from datetime import datetime from spyne.model import DateTime from spyne.model import Unicode from spyne.model import Integer from spyne.model import ComplexModel from spyne.interface.xml_schema.parser import hier_repr from spyne.util.xml import get_schema_documents from spyne.util.xml import get_object_as_xml from spyne.util.xml import get_xml_as_object from spyne.util.xml import parse_schema_string # Define the object class SomeObject(ComplexModel): i = Integer s = Unicode d = DateTime __repr__ = hier_repr # Instantiate the object instance = SomeObject(i=5, s="str", d=datetime.now()) # Generate schema documents schema_elts = get_schema_documents([SomeObject], 'some_ns') # Serialize the xml schema document for object schema = etree.tostring(schema_elts['tns'], pretty_print=True) # Serialize the object to XML instance_elt = get_object_as_xml(instance, SomeObject) # Serialize the element tree to string data = etree.tostring(instance_elt, pretty_print=True) print(instance) print() print(schema) print(data) # parse the schema document parsed_schema = parse_schema_string(schema)['some_ns'] # Get SomeObject definition from the parsed schema document NewObject = parsed_schema.types['SomeObject'] # We print an empty instance just to see the parsed fields. print(NewObject()) # Deserialize the xml document using the definition from the schema. new_instance = get_xml_as_object(etree.fromstring(data), NewObject) print(new_instance) assert new_instance.s == instance.s assert new_instance.i == instance.i assert new_instance.d == instance.dspyne-spyne-2.14.0/examples/xml/soap12-mtom/000077500000000000000000000000001417664205300205755ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/xml/soap12-mtom/.gitignore000066400000000000000000000000511417664205300225610ustar00rootroot00000000000000EA055406-5881-4F02-A3DC-9A5A7510D018.dat spyne-spyne-2.14.0/examples/xml/soap12-mtom/request.sh000077500000000000000000000011271417664205300226250ustar00rootroot00000000000000#!/bin/sh -x # The "boundary" string here must match with the one in the request body # (typically the first line of the request body) # The "start" string is similarly the content id of the first mime part # it is currently ignored by the MtoM parser curl -X POST \ -H 'Content-Type: multipart/related; type="application/xop+xml"; '` `'boundary="uuid:2e53e161-b47f-444a-b594-eb6b72e76997"; '` `'start=""; '` `'start-info="application/soap+xml"; '` `'action="sendDocument"' \ --data-binary @request.txt \ http://localhost:8000 spyne-spyne-2.14.0/examples/xml/soap12-mtom/request.txt000066400000000000000000000024221417664205300230260ustar00rootroot00000000000000 --uuid:2e53e161-b47f-444a-b594-eb6b72e76997 Content-Type: application/xop+xml; charset=UTF-8; type="application/soap+xml"; action="sendDocument"; Content-Transfer-Encoding: binary Content-ID: EA055406-5881-4F02-A3DC-9A5A7510D018.dat26981FCD51C95FA47780400B7A45132F --uuid:2e53e161-b47f-444a-b594-eb6b72e76997 Content-Type: application/octet-stream Content-Transfer-Encoding: binary Content-ID: <04dfbca1-54b8-4631-a556-4addea6716ed-223384@cxf.apache.org> 256 bytes:   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ --uuid:2e53e161-b47f-444a-b594-eb6b72e76997-- spyne-spyne-2.14.0/examples/xml/soap12-mtom/soap12_mtom.py000077500000000000000000000032741417664205300233210ustar00rootroot00000000000000#!/usr/bin/env python import logging logger = logging.getLogger(__name__) import os logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) from spyne.application import Application from spyne.decorator import rpc from spyne.service import ServiceBase from spyne.model.complex import ComplexModel from spyne.model.binary import ByteArray from spyne.model.primitive import Unicode from spyne.server.wsgi import WsgiApplication from spyne.protocol.soap import Soap12 tns = 'http://gib.gov.tr/vedop3/eFatura' class documentResponse(ComplexModel): msg = Unicode hash = ByteArray class GIBSoapService(ServiceBase): @rpc(Unicode(sub_name="fileName"), ByteArray(sub_name='binaryData'), ByteArray(sub_name="hash"), _returns=documentResponse) def documentRequest(ctx, file_name, file_data, data_hash): incoming_invoice_dir = os.getcwd() logger.info("file_name %r" % file_name) logger.info("file_hash: %r" % data_hash) path = os.path.join(incoming_invoice_dir, file_name) f = open(path, 'wb') for data in file_data: f.write(data) logger.info("File written: %r" % file_name) f.close() resp = documentResponse() resp.msg = "Document was written successfully" resp.hash = data_hash return resp application = Application([GIBSoapService], tns=tns, in_protocol=Soap12(), out_protocol=Soap12()) gib_application = WsgiApplication(application) from wsgiref.simple_server import make_server server = make_server('0.0.0.0', 8000, gib_application) server.serve_forever() spyne-spyne-2.14.0/examples/xml/soap_multi_ns.py000077500000000000000000000060671417664205300217600ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # """ This is a simple example that illustrates how to have immediate children of a complexType in a different namespace. """ from spyne import Unicode, Iterable, XmlAttribute, ComplexModel, Service, \ Application, rpc from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication NS_B = "www.example.com/schema/b" class Baz(ComplexModel): __namespace__ = NS_B Thing = Unicode AttrC = XmlAttribute(Unicode) class FooCustomRequest(ComplexModel): AttrA = XmlAttribute(Unicode) AttrB = XmlAttribute(Unicode) Bar = Baz.customize(sub_ns=NS_B) Baz = Unicode class FooService(Service): @rpc(FooCustomRequest, _returns = Iterable(Unicode), _body_style='bare') def Foo(ctx, req): AttrA, AttrB, Baz, Bar = req.AttrA, req.AttrB, req.Baz, req.Bar yield 'Hello, %s' % Bar application = Application([FooService], tns="www.example.com/schema/a", in_protocol=Soap11(validator='soft'), out_protocol=Soap11(), ) wsgi_application = WsgiApplication(application) if __name__ == '__main__': import logging from wsgiref.simple_server import make_server logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logging.info("listening to http://127.0.0.1:8000") logging.info("wsdl is at: http://localhost:8000/?wsdl") server = make_server('127.0.0.1', 8000, wsgi_application) server.serve_forever() spyne-spyne-2.14.0/examples/xml/thirdparty_schema.py000077500000000000000000000075171417664205300226170ustar00rootroot00000000000000#!/usr/bin/env python # !!!!!!!!!1111!!!!!!!!!! ATTENTION !!!!!11111!!!!!!!!!!!!! # This example is not finished. It doesn't work yet because # is not implemented # !!11111!!!!!!!11111!!!! ATTENTION !!!!!one!!!!!11111!!!!! from __future__ import print_function EXAMPLE_DOCUMENT = """ 2016-02-01T10:14:54.517Z 2016-02-01T10:19:54.517Z ... ... ... ... """ class NS: WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" DS = "http://www.w3.org/2000/09/xmldsig#" XSD = "http://www.w3.org/2001/XMLSchema" XML = "http://www.w3.org/XML/1998/namespace" files = { NS.WSSE: "wsse.xsd", NS.WSU: "wsu.xsd", NS.DS: "ds.xsd", NS.XSD: "xsd.xsd", NS.XML: "xml.xsd", } from os.path import isfile from datetime import datetime, timedelta from spyne import Service from spyne.util.xml import parse_schema_file for ns, fn in files.items(): if not isfile(fn): print("Missing file", fn) raise Exception("Please run 'make' in this script's directory to fetch" "schema files before running this example") import logging logging.basicConfig(level=logging.DEBUG) wsse = parse_schema_file(files[NS.WSSE], files=files) wsu = parse_schema_file(files[NS.WSU], files=files) class InteropServiceWithHeader(Service): __out_header__ = Security @rpc(_returns=Security) def send_out_header(ctx): ctx.out_header = Security( timestamp=TimeStamp( created=datetime.now(), expired=datetime.now() + timedelta(days=365), ) ) ctx.out_header.dt = datetime(year=2000, month=1, day=1) ctx.out_header.f = 3.141592653 return ctx.out_header spyne-spyne-2.14.0/examples/xml/utils.py000077500000000000000000000103741417664205300202400ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging logging.basicConfig(level=logging.DEBUG) import uuid from datetime import datetime from pprint import pprint from lxml import etree from lxml.builder import E from spyne import AnyXml, Uuid, Unicode, String, Integer, Decimal, DateTime, \ XmlData, ComplexModel, XmlAttribute from spyne.util.xml import get_schema_documents from spyne.util.xml import get_object_as_xml from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_validation_schema class Punk(ComplexModel): __namespace__ = 'some_namespace' a = String b = Integer c = Decimal d = DateTime class DateTimeWithAttribute(ComplexModel): value = XmlData(DateTime) f = XmlAttribute(Unicode) class Foo(ComplexModel): __namespace__ = 'some_other_namespace' a = String b = Integer c = Decimal d = DateTimeWithAttribute e = XmlAttribute(Integer) class ProductEdition(ComplexModel): __namespace__ = 'kickass_namespace' id = XmlAttribute(Uuid) name = XmlData(Unicode) addtl = XmlData(AnyXml) class Product(ComplexModel): __namespace__ = 'kickass_namespace' id = XmlAttribute(Uuid) edition = ProductEdition docs = get_schema_documents([Punk, Foo, Product]) pprint(docs) print() pprint({k: v.attrib['targetNamespace'] for k, v in docs.items()}) # the default ns prefix is always tns print("the default namespace %r:" % docs['tns'].attrib['targetNamespace']) print(etree.tostring(docs['tns'], pretty_print=True, encoding='unicode')) print() # Namespace prefixes are assigned like s0, s1, s2, etc... print("the other namespace %r:" % docs['s0'].attrib['targetNamespace']) print(etree.tostring(docs['s0'], pretty_print=True, encoding='unicode')) print() print("the other namespace %r:" % docs['s2'].attrib['targetNamespace']) print(etree.tostring(docs['s2'], pretty_print=True, encoding='unicode')) print() # Object serialization and deserialization foo = Foo( a='a', b=1, c=3.4, d=DateTimeWithAttribute(value=datetime(2011, 2, 20), f='f'), e=5, ) doc = get_object_as_xml(foo, Foo) print() print(etree.tostring(doc, pretty_print=True, encoding='unicode')) print(get_xml_as_object(doc, Foo)) print() # could be anything, really elt = E.tag(E.subtag("subdata")) # XmlData example. print("Product output (illustrates XmlData):") product = Product( id=uuid.uuid4(), edition=ProductEdition(id=uuid.uuid4(), name='My edition', addtl=elt) ) print() print(etree.tostring( get_object_as_xml(product, Product), pretty_print=True, encoding='unicode' )) # See http://lxml.de/validation.html to see what this could be used for. print(get_validation_schema([Punk, Foo])) spyne-spyne-2.14.0/examples/xml/utils_poly.py000077500000000000000000000013351417664205300213000ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import sys from lxml import etree from spyne.util import six from spyne import ComplexModel, Unicode from spyne.util.xml import get_object_as_xml_polymorphic class B(ComplexModel): _type_info = [ ('_b', Unicode(default="b")), ] class C(B): _type_info = [ ('_c', Unicode(default="c")), ] class A(ComplexModel): _type_info = [ ('a', Unicode(subname="_a")), ('b', B.customize(subname="_b")), ] a = A(b=C()) elt = get_object_as_xml_polymorphic(a, A, no_namespace=True) xml_string = etree.tostring(elt, pretty_print=True) if six.PY2: print(xml_string, end="") else: sys.stdout.buffer.write(xml_string) spyne-spyne-2.14.0/examples/xml/validation_error/000077500000000000000000000000001417664205300220615ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/xml/validation_error/README000066400000000000000000000001661417664205300227440ustar00rootroot00000000000000 The scripts in this file illustrate how libxml's validation break when the Xml parser has resolve_entities disabled. spyne-spyne-2.14.0/examples/xml/validation_error/inst.xml000066400000000000000000000002421417664205300235560ustar00rootroot00000000000000 ]> 2013-07-15T20:21:43.476838 5 &file; spyne-spyne-2.14.0/examples/xml/validation_error/py_output000066400000000000000000000006131417664205300240540ustar00rootroot00000000000000Traceback (most recent call last): File "validation_internal_error.py", line 31, in schema.validate(elt) File "lxml.etree.pyx", line 3252, in lxml.etree._Validator.validate (src/lxml/lxml.etree.c:143796) File "xmlschema.pxi", line 151, in lxml.etree.XMLSchema.__call__ (src/lxml/lxml.etree.c:152165) lxml.etree.XMLSchemaValidateError: Internal error in XML Schema validation. spyne-spyne-2.14.0/examples/xml/validation_error/schema.xsd000066400000000000000000000020761417664205300240460ustar00rootroot00000000000000 spyne-spyne-2.14.0/examples/xml/validation_error/sh_output000066400000000000000000000012051417664205300240340ustar00rootroot00000000000000inst.xml:5: namespace warning : xmlns: URI some_ns is not absolute ^ ]> 2013-07-15T20:21:43.476838 5 &file; inst.xml:8: element s: Schemas validity error : Internal error: xmlSchemaVDocWalk, there is at least one entity reference in the node-tree currently being validated. Processing of entities with this XML Schema processor is not supported (yet). Please substitute entities before validation.. inst.xml validation generated an internal error spyne-spyne-2.14.0/examples/xml/validation_error/some_file000066400000000000000000000000221417664205300237400ustar00rootroot00000000000000some_file_contentsspyne-spyne-2.14.0/examples/xml/validation_error/validation_internal_error.py000077500000000000000000000004511417664205300276750ustar00rootroot00000000000000#!/usr/bin/env python from lxml.etree import XMLParser, fromstring, XMLSchema schema_doc = open('schema.xsd').read() inst_doc = open('inst.xml').read() parser = XMLParser(resolve_entities=False) elt = fromstring(inst_doc, parser) schema = XMLSchema(fromstring(schema_doc)) schema.validate(elt) spyne-spyne-2.14.0/examples/xml/validation_error/validation_internal_error.sh000077500000000000000000000000611417664205300276540ustar00rootroot00000000000000#!/bin/sh xmllint --schema schema.xsd inst.xml spyne-spyne-2.14.0/examples/zeromq/000077500000000000000000000000001417664205300172335ustar00rootroot00000000000000spyne-spyne-2.14.0/examples/zeromq/app.py000066400000000000000000000041631417664205300203710ustar00rootroot00000000000000# # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from spyne.application import Application from spyne.protocol.msgpack import MessagePackRpc from spyne.service import Service from spyne.decorator import rpc from spyne.model.primitive import Unicode class RadianteRPC(Service): @rpc(_returns=Unicode) def whoami(ctx): return "Hello I am Seldon!" app = Application( [RadianteRPC], tns="radiante.rpc", in_protocol=MessagePackRpc(validator="soft"), out_protocol=MessagePackRpc() ) import logging logging.basicConfig(level=logging.DEBUG) spyne-spyne-2.14.0/examples/zeromq/client.py000077500000000000000000000034071417664205300210720ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # from app import app from spyne.client.zeromq import ZeroMQClient c = ZeroMQClient('tcp://localhost:5001', app) print c.service.whoami() spyne-spyne-2.14.0/examples/zeromq/server.py000077500000000000000000000035001417664205300211140ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # Copyright © Burak Arslan , # Arskom Ltd. http://www.arskom.com.tr # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import logging from app import app from spyne.server.zeromq import ZeroMQServer URL = "tcp://127.0.0.1:5001" logging.info("Listening to %r", URL) s = ZeroMQServer(app, URL) s.serve_forever() spyne-spyne-2.14.0/pytest.ini000066400000000000000000000000351417664205300161270ustar00rootroot00000000000000[pytest] junit_family=xunit1 spyne-spyne-2.14.0/requirements/000077500000000000000000000000001417664205300166235ustar00rootroot00000000000000spyne-spyne-2.14.0/requirements/test_django_req.txt000066400000000000000000000001041417664205300225270ustar00rootroot00000000000000pytest pytest_django pytest_cov pytest_twisted junitxml lxml twistedspyne-spyne-2.14.0/requirements/test_requirements.txt000066400000000000000000000003431417664205300231460ustar00rootroot00000000000000pytest>=2.9 pytest-twisted pytest-cov coverage junitxml werkzeug sqlalchemy lxml>=3.6 pyyaml pyzmq twisted colorama msgpack>=1 webtest pytest_django django python-subunit pyramid tox pyparsing>=2.0.2 suds-community zeep pandas spyne-spyne-2.14.0/requirements/test_requirements_py27.txt000066400000000000000000000003711417664205300240300ustar00rootroot00000000000000pytest>=2.9 pytest-twisted pytest-cov coverage junitxml werkzeug sqlalchemy lxml>=3.6 pyyaml pyzmq twisted colorama msgpack-python webtest pytest_django django python-subunit pyramid tox pyparsing>=2.0.2 suds-jurko zeep numpy<1.16.99 pandas<0.24.99 spyne-spyne-2.14.0/run_tests.sh000077500000000000000000000131061417664205300164660ustar00rootroot00000000000000#!/bin/bash -x # # Sets up a Python testing environment from scratch. Mainly written for Jenkins. # Works for CPython. Not working for Jython, IronPython and PyPy. # # Requirements: # A working build environment inside the container with OpenSSL, bzip2, # libxml2 and libxslt development files. Only tested on Linux variants. # # Last time we looked, for ubuntu, that meant: # $ sudo apt-get install build-essential libssl-dev lilbbz2-dev \ # libxml2-dev libxslt-dev # # Usage: # Example: # # $ PYFLAV=cpy-3.3 ./run_tests.sh # # Variables: # - PYFLAV: Defaults to 'cpy-2.7'. See other values below. # - WORKSPACE: Defaults to $PWD. It's normally set by Jenkins. # - MAKEOPTS: Defaults to '-j2'. # - MONOVER: Defaults to '2.11.4'. Only relevant for ipy-* flavors. # # Jenkins guide: # 1. Create a 'Multi configuration project'. # 2. Set up stuff like git repo the usual way. # 3. In the 'Configuration Matrix' section, create a user-defined axis named # 'PYVER'. and set it to the Python versions you'd like to test, separated # by whitespace. For example: 'cpy-2.7 cpy-3.4' # 4. Add a new "Execute Shell" build step and type in './run_tests.sh'. # 5. Add a new "Publish JUnit test report" post-build action and type in # 'test_result.*.xml' # 6. Add a new "Publish Cobertura Coverage Report" post-build action and type # in 'coverage.xml'. Install the "Cobertura Coverage Report" plug-in if you # don't see this option. # 7. Nonprofit! # # Sanitization [ -z "$PYFLAV" ] && PYFLAV=cpy-2.7; [ -z "$MONOVER" ] && MONOVER=2.11.4; [ -z "$WORKSPACE" ] && WORKSPACE="$PWD"; [ -z "$MAKEOPTS" ] && MAKEOPTS="-j2"; PYIMPL=(${PYFLAV//-/ }); PYVER=${PYIMPL[1]}; PYFLAV="${PYFLAV/-/}"; PYFLAV="${PYFLAV/./}"; if [ -z "$PYVER" ]; then PYVER=${PYIMPL[0]}; PYIMPL=cpy; PYFLAV=cpy${PYVER/./}; else PYIMPL=${PYIMPL[0]}; fi PYNAME=python$PYVER; if [ -z "$FN" ]; then declare -A URLS; URLS["cpy27"]="2.7.18/Python-2.7.18.tar.xz"; URLS["cpy36"]="3.6.15/Python-3.6.15.tar.xz"; URLS["cpy37"]="3.7.12/Python-3.7.12.tar.xz"; URLS["cpy38"]="3.8.12/Python-3.8.12.tar.xz"; URLS["cpy39"]="3.9.10/Python-3.9.10.tar.xz"; URLS["cpy310"]="3.10.2/Python-3.10.2.tar.xz"; URLS["jyt27"]="2.7.1/jython-installer-2.7.1.jar"; URLS["ipy27"]="ipy-2.7.4.zip"; FN="${URLS["$PYFLAV"]}"; if [ -z "$FN" ]; then echo "Unknown Python version $PYFLAV"; exit 2; fi; fi; # Initialization IRONPYTHON_URL_BASE=https://github.com/IronLanguages/main/archive; CPYTHON_URL_BASE=http://www.python.org/ftp/python; JYTHON_URL_BASE=http://search.maven.org/remotecontent?filepath=org/python/jython-installer; MAKE="make $MAKEOPTS"; # Set specific variables if [ $PYIMPL == "cpy" ]; then PREFIX="$(basename $(basename $FN .tgz) .tar.xz)"; elif [ $PYIMPL == "ipy" ]; then PREFIX="$(basename $FN .zip)"; MONOPREFIX="$WORKSPACE/mono-$MONOVER" XBUILD="$MONOPREFIX/bin/xbuild" elif [ $PYIMPL == "jyt" ]; then PYNAME=jython; PREFIX="$(basename $FN .jar)"; fi; # Set common variables PYTHON="$WORKSPACE/$PREFIX/bin/$PYNAME"; PIP="$WORKSPACE/$PREFIX/bin/pip$PYVER"; TOX="$WORKSPACE/$PREFIX/bin/tox"; TOX2="$HOME/.local/bin/tox" # Set up requested python environment. if [ $PYIMPL == 'cpy' ]; then if [ ! -x "$PYTHON" ]; then ( mkdir -p .data; cd .data; wget -ct0 $CPYTHON_URL_BASE/$FN; tar xf $(basename $FN); cd "$PREFIX"; if [ ! -f "Makefile" ]; then ./configure --prefix="$WORKSPACE/$PREFIX" --without-pydebug --with-ensurepip; fi $MAKE && make install; ); fi; export PATH="$WORKSPACE/$PREFIX/bin":"$PATH"; elif [ $PYIMPL == 'jyt' ]; then if [ ! -x "$PYTHON" ]; then ( mkdir -p .data; cd .data; FILE=$(basename $FN); wget -O $FILE -ct0 "$JYTHON_URL_BASE/$FN"; java -jar $FILE -s -d "$WORKSPACE/$PREFIX" ); fi elif [ $PYIMPL == 'ipy' ]; then # Set up Mono first # See: http://www.mono-project.com/Compiling_Mono_From_Tarball if [ ! -x "$XBUILD" ]; then ( mkdir -p .data; cd .data; wget -ct0 http://download.mono-project.com/sources/mono/mono-$MONOVER.tar.bz2 tar xf mono-$MONOVER.tar.bz2; cd mono-$MONOVER; ./configure --prefix=$WORKSPACE/mono-$MONOVER; $MAKE && make install; ); fi # Set up IronPython # See: https://github.com/IronLanguages/main/wiki/Building#the-mono-runtime if [ ! -x "$PYTHON" ]; then ( mkdir -p .data; cd .data; export PATH="$(dirname "$XBUILD"):$PATH" wget -ct0 "$IRONPYTHON_URL_BASE/$FN"; unzip -q "$FN"; cd "main-$PREFIX"; $XBUILD /p:Configuration=Release Solutions/IronPython.sln || exit 1 mkdir -p "$(dirname "$PYTHON")"; echo 'mono "$PWD/bin/Release/ir.exe" "${@}"' > $PYTHON; chmod +x $PYTHON; ) || exit 1; fi; fi; # Set up pip $PYTHON -m ensurepip --upgrade || exit 10; # Set up tox if [ ! -x "$TOX" ]; then $PIP install tox || exit 11; fi; set "$PIP" install cython || exit 12 if [ "$PYVER" == "2.7" ]; then "$PIP" install numpy\<1.16.99 || exit 13; "$PIP" install -rrequirements/test_requirements_py27.txt || exit 14; else "$PIP" install numpy || exit 15; "$PIP" install -rrequirements/test_requirements.txt || exit 16; fi [ -e .coverage ] && rm -v .coverage [ -e .coverage ] && rm -v coverage.xml export PYTHONHASHSEED=0 # ignore return value -- result information is in the produced xml files "$PYTHON" setup.py test || true; spyne-spyne-2.14.0/setup.py000077500000000000000000000210141417664205300156130ustar00rootroot00000000000000#!/usr/bin/env python #encoding: utf8 from __future__ import print_function import io import os import re import sys import inspect from glob import glob from itertools import chain from os.path import join, dirname, abspath from setuptools import setup from setuptools import find_packages from setuptools.command.test import test as TestCommand try: import colorama colorama.init() from colorama import Fore RESET = Fore.RESET GREEN = Fore.GREEN RED = Fore.RED except ImportError: RESET = '' GREEN = '' RED = '' IS_PYPY = '__pypy__' in sys.builtin_module_names OWN_PATH = abspath(inspect.getfile(inspect.currentframe())) EXAMPLES_DIR = join(dirname(OWN_PATH), 'examples') PYVER = ''.join([str(i) for i in sys.version_info[:2]]) with io.open(os.path.join(os.path.dirname(__file__), 'spyne', '__init__.py'), 'r') as v: VERSION = re.match(r".*__version__ = '(.*?)'", v.read(), re.S).group(1) SHORT_DESC="A transport and architecture agnostic rpc library that focuses on" \ " exposing public services with a well-defined API." LONG_DESC = """Homepage: http://spyne.io Spyne aims to save the protocol implementers the hassle of implementing their own remote procedure call api and the application programmers the hassle of jumping through hoops just to expose their services using multiple protocols and transports. """ try: os.stat('CHANGELOG.rst') with io.open('CHANGELOG.rst', 'rb') as f: LONG_DESC += u"\n\n" + f.read().decode('utf8') except OSError: pass ############################### # Testing stuff def call_test(f, a, tests, env={}): import spyne.test from multiprocessing import Process, Queue tests_dir = os.path.dirname(spyne.test.__file__) if len(tests) > 0: a.extend(chain(*[glob(join(tests_dir, test)) for test in tests])) queue = Queue() p = Process(target=_wrapper(f), args=[a, queue, env]) p.start() p.join() ret = queue.get() if ret == 0: print(tests or a, "OK") else: print(tests or a, "FAIL") print() return ret def _wrapper(f): import traceback def _(args, queue, env): print("env:", env) for k, v in env.items(): os.environ[k] = v try: retval = f(args) except SystemExit as e: retval = e.code except BaseException as e: print(traceback.format_exc()) retval = 1 queue.put(retval) return _ def run_tests_and_create_report(report_name, *tests, **kwargs): import spyne.test import pytest if os.path.isfile(report_name): os.unlink(report_name) tests_dir = os.path.dirname(spyne.test.__file__) args = [ '--verbose', '--cov-report=', '--cov', 'spyne', '--cov-append', '--tb=short', '--junitxml=%s' % report_name, ] args.extend('--{0}={1}'.format(k, v) for k, v in kwargs.items()) args.extend(chain(*[glob("%s/%s" % (tests_dir, test)) for test in tests])) return pytest.main(args) _ctr = 0 def call_pytest(*tests, **kwargs): global _ctr _ctr += 1 file_name = 'test_result.%d.xml' % _ctr os.environ['COVERAGE_FILE'] = '.coverage.%d' % _ctr return run_tests_and_create_report(file_name, *tests, **kwargs) def call_pytest_subprocess(*tests, **kwargs): global _ctr import pytest _ctr += 1 file_name = 'test_result.%d.xml' % _ctr if os.path.isfile(file_name): os.unlink(file_name) # env = {'COVERAGE_FILE': '.coverage.%d' % _ctr} env = {} args = [ '--verbose', '--cov-append', '--cov-report=', '--cov', 'spyne', '--tb=line', '--junitxml=%s' % file_name ] args.extend('--{0}={1}'.format(k, v) for k, v in kwargs.items()) return call_test(pytest.main, args, tests, env) def call_tox_subprocess(env): import tox.session args = ['-e', env] return call_test(tox.session.main, args, []) def call_coverage(): import coverage.cmdline # coverage.cmdline.main(['combine']) # call_test(coverage.cmdline.main, ['combine'], []) call_test(coverage.cmdline.main, ['xml', '-i'], []) return 0 class ExtendedTestCommand(TestCommand): """TestCommand customized to project needs.""" user_options = TestCommand.user_options + [ ('capture=', 'k', "py.test output capture control (see py.test " "--capture)"), ] def initialize_options(self): TestCommand.initialize_options(self) self.capture = 'fd' def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True class RunTests(ExtendedTestCommand): def run_tests(self): cfn = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), 'tox.ini') from collections import OrderedDict djenvs = tuple(OrderedDict(((k, None) for k in re.findall('py%s-dj[0-9]+' % PYVER, open(cfn, 'rb').read().decode('utf8')))).keys()) ret = 0 tests = [ 'interface', 'model', 'multipython', 'protocol', 'util', 'interop/test_pyramid.py', 'interop/test_soap_client_http_twisted.py', 'transport/test_msgpack.py' 'test_null_server.py', 'test_service.py', 'test_soft_validation.py', 'test_sqlalchemy.py', 'test_sqlalchemy_deprecated.py', ] print("Test stage 1: Unit tests") ret = call_pytest_subprocess(*tests, capture=self.capture) or ret print("\nTest stage 2: End-to-end tests") ret = call_pytest_subprocess('interop/test_httprpc.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_soap_client_http.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_soap_client_zeromq.py', capture=self.capture) or ret # excluding PyPy as it chokes here on LXML if not IS_PYPY: ret = call_pytest_subprocess('interop/test_suds.py', capture=self.capture) or ret ret = call_pytest_subprocess('interop/test_zeep.py', capture=self.capture) or ret print("\nTest stage 3: Tox-managed tests") for djenv in djenvs: ret = call_tox_subprocess(djenv) or ret if ret == 0: print(GREEN + "All that glisters is not gold." + RESET) else: print(RED + "Something is rotten in the state of Denmark." + RESET) print ("Generating coverage.xml") call_coverage() raise SystemExit(ret) # Testing stuff ends here. ############################### setup( name='spyne', packages=find_packages(), version=VERSION, description=SHORT_DESC, long_description=LONG_DESC, classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', #'Programming Language :: Python :: Implementation :: Jython', 'Programming Language :: Python :: Implementation :: PyPy', 'Operating System :: OS Independent', 'Natural Language :: English', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], keywords='soap wsdl wsgi zeromq rest rpc json http msgpack xml' ' django pyramid postgresql sqlalchemy twisted yaml'.split(), author='Burak Arslan', author_email='burak+package@spyne.io', maintainer='Burak Arslan', maintainer_email='burak+package@spyne.io', url='http://spyne.io', license='LGPL-2.1', zip_safe=False, install_requires=[ 'pytz', ], entry_points={ 'console_scripts': [ 'sort_wsdl=spyne.test.sort_wsdl:main', ] }, cmdclass={ 'test': RunTests, }, ) spyne-spyne-2.14.0/spyne/000077500000000000000000000000001417664205300152365ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/__init__.py000066400000000000000000000054051417664205300173530ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # class LogicError(Exception): pass __version__ = '2.14.0' from pytz import utc as LOCAL_TZ from decimal import Decimal as D DEFAULT_LANGUAGE = 'en' from spyne._base import Address from spyne.context import AuxMethodContext from spyne.context import TransportContext from spyne.context import ProtocolContext from spyne.context import EventContext from spyne.context import MethodContext from spyne.evmgr import EventManager from spyne.descriptor import MethodDescriptor from spyne.descriptor import BODY_STYLE_WRAPPED from spyne.descriptor import BODY_STYLE_BARE from spyne.descriptor import BODY_STYLE_OUT_BARE from spyne.descriptor import BODY_STYLE_EMPTY from spyne.descriptor import BODY_STYLE_EMPTY_OUT_BARE # decorator imports descriptor, so this needs to come after from spyne.decorator import rpc from spyne.decorator import srpc from spyne.decorator import mrpc from spyne.service import ServiceBase as Service from spyne.service import ServiceBase # DEPRECATED from spyne.interface import Interface from spyne.interface import InterfaceDocuments from spyne.interface import InterfaceDocumentsBase from spyne.application import Application from spyne.model import * from spyne.model import Mandatory as M from spyne.error import InvalidCredentialsError from spyne.error import RequestTooLongError from spyne.error import RequestNotAllowed from spyne.error import ArgumentError from spyne.error import InvalidInputError from spyne.error import MissingFieldError from spyne.error import ValidationError from spyne.error import InternalError from spyne.error import ResourceNotFoundError from spyne.error import RespawnError from spyne.error import ResourceAlreadyExistsError from spyne.error import Redirect from spyne.client import ClientBase, RemoteProcedureBase, RemoteService from spyne.server import ServerBase, NullServer def _vercheck(): import sys if not hasattr(sys, "version_info") or sys.version_info < (2, 6): raise RuntimeError("Spyne requires Python 2.6 or later. Trust us.") _vercheck() spyne-spyne-2.14.0/spyne/_base.py000066400000000000000000000030561417664205300166650ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger('spyne') from collections import namedtuple # When spyne.server.twisted gets imported, this type gets a static method named # `from_twisted_address`. Dark magic. Address = namedtuple("Address", ["type", "host", "port"]) class _add_address_types(): Address.TCP4 = 'TCP4' Address.TCP6 = 'TCP6' Address.UDP4 = 'UDP4' Address.UDP6 = 'UDP6' def address_str(self): return ":".join((self.type, self.host, str(self.port))) Address.__str__ = address_str # this gets overwritten once spyne.server.twisted is imported @staticmethod def _fta(*a, **kw): from spyne.server.twisted._base import _address_from_twisted_address return _address_from_twisted_address(*a, **kw) Address.from_twisted_address = _fta spyne-spyne-2.14.0/spyne/application.py000066400000000000000000000301401417664205300201110ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) logger_client = logging.getLogger('.'.join([__name__, 'client'])) logger_server = logging.getLogger('.'.join([__name__, 'server'])) from pprint import pformat from spyne import BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_WRAPPED, \ EventManager from spyne.error import Fault, Redirect, RespawnError, InvalidRequestError from spyne.interface import Interface, InterfaceDocuments from spyne.util import six from spyne.util.appreg import register_application class MethodAlreadyExistsError(Exception): def __init__(self, what): super(MethodAlreadyExistsError, self) \ .__init__("Method key %r already exists", what) def get_fault_string_from_exception(e): # haha. return "Internal Error" def return_traceback_in_unhandled_exceptions(): """Call this function first thing in your main function to return original python errors to your clients in case of unhandled exceptions. """ global get_fault_string_from_exception import traceback def _get_fault_string_from_exception(e): return traceback.format_exc() get_fault_string_from_exception = _get_fault_string_from_exception class Application(object): """The Application class is the glue between one or more service definitions, input and output protocols. :param services: An iterable of Service subclasses that defines the exposed services. :param tns: The targetNamespace attribute of the exposed service. :param name: The optional name attribute of the exposed service. The default is the name of the application class which is by default 'Application'. :param in_protocol: A ProtocolBase instance that denotes the input protocol. It's only optional for NullServer transport. :param out_protocol: A ProtocolBase instance that denotes the output protocol. It's only optional for NullServer transport. :param config: An arbitrary python object to store random global data. :param classes: An iterable of Spyne classes that don't appear in any of the service definitions but need to appear in the interface documents nevertheless. :param documents_container: A class that implements the InterfaceDocuments interface Supported events: * ``method_call``: Called right before the service method is executed * ``method_return_object``: Called right after the service method is executed * ``method_exception_object``: Called when an exception occurred in a service method, before the exception is serialized. * ``method_context_created``: Called from the constructor of the MethodContext instance. * ``method_context_closed``: Called from the ``close()`` function of the MethodContext instance, which in turn is called by the transport when the response is fully sent to the client (or in the client case, the response is fully received from server). """ transport = None def __init__(self, services, tns, name=None, in_protocol=None, out_protocol=None, config=None, classes=(), documents_container=InterfaceDocuments): self.services = tuple(services) self.tns = tns self.name = name self.config = config self.classes = classes if self.name is None: self.name = self.__class__.__name__.split('.')[-1] logger.info("Initializing application {%s}%s...", self.tns, self.name) self.event_manager = EventManager(self) self.error_handler = None self.in_protocol = in_protocol self.out_protocol = out_protocol if self.in_protocol is None: from spyne.protocol import ProtocolBase self.in_protocol = ProtocolBase() if self.out_protocol is None: from spyne.protocol import ProtocolBase self.out_protocol = ProtocolBase() self.check_unique_method_keys() # is this really necessary nowadays? # this needs to be after protocol assignments to give _static_when # functions as much info as possible about the application self.interface = Interface(self, documents_container=documents_container) # set_app needs to be after interface init because the protocols use it. self.in_protocol.set_app(self) # FIXME: this normally is another parameter to set_app but it's kept # separate for backwards compatibility reasons. self.in_protocol.message = self.in_protocol.REQUEST self.out_protocol.set_app(self) # FIXME: this normally is another parameter to set_app but it's kept # separate for backwards compatibility reasons. self.out_protocol.message = self.out_protocol.RESPONSE register_application(self) def process_request(self, ctx): """Takes a MethodContext instance. Returns the response to the request as a native python object. If the function throws an exception, it returns None and sets the exception object to ctx.out_error. Overriding this method would break event management. So this is not meant to be overridden unless you know what you're doing. """ try: ctx.fire_event('method_call') # in object is always a sequence of incoming values. We need to fix # that for bare mode. if ctx.descriptor.body_style is BODY_STYLE_BARE: ctx.in_object = [ctx.in_object] elif ctx.descriptor.body_style is BODY_STYLE_EMPTY: ctx.in_object = [] # call user method ctx.out_object = self.call_wrapper(ctx) # out object is always a sequence of return values. see # MethodContext docstrings for more info if ctx.descriptor.body_style is not BODY_STYLE_WRAPPED or \ len(ctx.descriptor.out_message._type_info) <= 1: # if it's not a wrapped method, OR there's just one return type # we wrap it ourselves ctx.out_object = [ctx.out_object] # Now that the processing is switched to the outgoing message, # point ctx.protocol to ctx.out_protocol ctx.protocol = ctx.outprot_ctx ctx.fire_event('method_return_object') except Redirect as e: try: e.do_redirect() ctx.out_object = [None] # Now that the processing is switched to the outgoing message, # point ctx.protocol to ctx.out_protocol ctx.protocol = ctx.outprot_ctx ctx.fire_event('method_redirect') except Exception as e: logger_server.exception(e) ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) ctx.fire_event('method_redirect_exception') except Fault as e: if e.faultcode == 'Client' or e.faultcode.startswith('Client.'): logger_client.exception(e) else: logger.exception(e) ctx.out_error = e ctx.fire_event('method_exception_object') # we don't catch BaseException because we actually don't want to catch # "system-exiting" exceptions. See: # https://docs.python.org/2/library/exceptions.html#exceptions.Exception except Exception as e: logger_server.critical(e, **{'exc_info': 1}) ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) ctx.fire_event('method_exception_object') def call_wrapper(self, ctx): """This method calls the call_wrapper method in the service definition. This can be overridden to make an application-wide custom exception management. """ # no function if ctx.function is None: logger.debug("Skipping user code call as ctx.function is None.") return None # @rpc inside service class if ctx.descriptor.no_self: assert ctx.descriptor.service_class is not None return ctx.descriptor.service_class.call_wrapper(ctx) # from here on it's @mrpc in a (parent) class cls = ctx.descriptor.parent_class if cls.__orig__ is not None: cls = cls.__orig__ filters = {} inst = cls.__respawn__(ctx, filters) if inst is None: raise RespawnError('{%s}%s with params %r' % (cls.get_namespace(), cls.get_type_name(), filters)) in_cls = ctx.descriptor.in_message args = tuple(ctx.in_object) if args is None: args = () elif ctx.descriptor.body_style is BODY_STYLE_WRAPPED and \ len(in_cls.get_flat_type_info(in_cls)) <= 1: args = () else: args = args[1:] # check whether this is a valid request according to the prerequisite # function (the callable that was passed in the _when argument to @mrpc) if ctx.descriptor.when is not None: if not ctx.descriptor.when(inst, ctx): raise InvalidRequestError("Invalid object state for request") if ctx.descriptor.no_ctx: args = (inst,) + args else: args = (inst, ctx,) + args if ctx.descriptor.service_class is None: retval = ctx.function(*args) else: retval = ctx.descriptor.service_class.call_wrapper(ctx, args=args) return retval def _has_callbacks(self): return self.interface._has_callbacks() def reinitialize(self, server): """This is normally called on transport instantiation by ServerBase""" seen = set() from spyne import MethodDescriptor for d in self.interface.method_id_map.values(): assert isinstance(d, MethodDescriptor) if d.aux is not None and not id(d.aux) in seen: d.aux.initialize(server) seen.add(id(d.aux)) if d.service_class is not None and not id(d.service_class) in seen: d.service_class.initialize(server) seen.add(id(d.service_class)) def __hash__(self): return hash(tuple((id(s) for s in self.services))) def check_unique_method_keys(self): keys = {} for s in self.services: for mdesc in s.public_methods.values(): other_mdesc = keys.get(mdesc.internal_key, None) if other_mdesc is not None: logger.error( 'Methods keys for "%s.%s" and "%s.%s" conflict', mdesc.function.__module__, six.get_function_name(mdesc.function), other_mdesc.function.__module__, six.get_function_name(other_mdesc.function)) raise MethodAlreadyExistsError(mdesc.internal_key) keys[mdesc.internal_key] = mdesc spyne-spyne-2.14.0/spyne/auxproc/000077500000000000000000000000001417664205300167175ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/auxproc/__init__.py000066400000000000000000000032451417664205300210340ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.auxproc`` package contains backends to process auxiliary method contexts. "Auxiliary Methods" are methods that run asyncronously once the primary method returns (either successfully or not). There can be only one primary method for a given method identifier but zero or more auxiliary methods. To define multiple auxiliary methods for a given main method, you must use separate :class:`Service` subclasses that you pass to the :class:`spyne.application.Application` constructor. Auxiliary methods are a useful abstraction for a variety of asyncronous execution methods like persistent or non-persistent queueing, async execution in another thread, process or node. Classes from this package will have the ``AuxProc`` suffix, short for "Auxiliary Processor". This package is DEPRECATED. Get rid of this ASAP. """ from spyne.auxproc._base import process_contexts from spyne.auxproc._base import AuxProcBase spyne-spyne-2.14.0/spyne/auxproc/_base.py000066400000000000000000000056051417664205300203500ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Base class and other helper methods for Auxiliary Method Processors ('AuxProc's for short). AuxProcs define how an auxiliary method is going to be executed. """ import logging logger = logging.getLogger(__name__) from spyne import AuxMethodContext def process_contexts(server, contexts, p_ctx, error=None): """Method to be called in the auxiliary context.""" for ctx in contexts: ctx.descriptor.aux.initialize_context(ctx, p_ctx, error) if error is None or ctx.descriptor.aux.process_exceptions: ctx.descriptor.aux.process_context(server, ctx) class AuxProcBase(object): def __init__(self, process_exceptions=False): """Abstract Base class shared by all AuxProcs. :param process_exceptions: If false, does not execute auxiliary methods when the main method throws an exception. """ self.methods = [] self.process_exceptions = process_exceptions def process(self, server, ctx, *args, **kwargs): """The method that does the actual processing. This should be called from the auxiliary context. """ server.get_in_object(ctx) if ctx.in_error is not None: logger.exception(ctx.in_error) return ctx.in_error server.get_out_object(ctx) if ctx.out_error is not None: logger.exception(ctx.out_error) return ctx.out_error server.get_out_string(ctx) for s in ctx.out_string: pass ctx.close() def process_context(self, server, ctx, p_ctx, p_error): """Override this to implement your own auxiliary processor.""" raise NotImplementedError() def initialize(self, server): """Override this method to make arbitrary initialization of your AuxProc. It's called once, 'as late as possible' into the Application initialization.""" def initialize_context(self, ctx, p_ctx, error): """Override this method to alter thow the auxiliary method context is initialized. It's called every time the method is executed. """ ctx.aux = AuxMethodContext(p_ctx, error) spyne-spyne-2.14.0/spyne/auxproc/sync.py000066400000000000000000000020771417664205300202530ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from spyne.auxproc import AuxProcBase class SyncAuxProc(AuxProcBase): """SyncAuxProc processes auxiliary methods synchronously. It's useful for testing purposes. """ def process_context(self, server, ctx): self.process(server, ctx) spyne-spyne-2.14.0/spyne/auxproc/thread.py000066400000000000000000000034041417664205300205410ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from multiprocessing.pool import ThreadPool from spyne.auxproc import AuxProcBase class ThreadAuxProc(AuxProcBase): """ThreadAuxProc processes auxiliary methods asynchronously in another thread using the undocumented ``multiprocessing.pool.ThreadPool`` class. This is available in Python 2.7. It's possibly there since 2.6 as well but it's hard to tell since it's not documented. :param pool_size: Max. number of threads that can be used to process methods in auxiliary queue in parallel. """ def __init__(self, pool_size=1): super(ThreadAuxProc, self).__init__() self.pool = None self.__pool_size = pool_size @property def pool_size(self): return self.__pool_size def process_context(self, server, ctx, *args, **kwargs): self.pool.apply_async(self.process, (server, ctx) + args, kwargs) def initialize(self, server): self.pool = ThreadPool(self.__pool_size) spyne-spyne-2.14.0/spyne/client/000077500000000000000000000000001417664205300165145ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/client/__init__.py000066400000000000000000000017741417664205300206360ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.client`` package contains the client transports.""" from spyne.client._base import Factory from spyne.client._base import RemoteService from spyne.client._base import ClientBase from spyne.client._base import RemoteProcedureBase spyne-spyne-2.14.0/spyne/client/_base.py000066400000000000000000000140011417664205300201330ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Contains the ClientBase class and its helper objects.""" from spyne.context import MethodContext from spyne.model.primitive import string_encoding class Factory(object): def __init__(self, app): self.__app = app def create(self, object_name): return self.__app.interface.get_class_instance(object_name) class RemoteService(object): def __init__(self, rpc_class, url, app, *args, **kwargs): self.__app = app self.__url = url self.out_header = None self.rpc_class = rpc_class self.args = args self.kwargs = kwargs def __getattr__(self, key): return self.rpc_class(self.__url, self.__app, key, self.out_header, *self.args, **self.kwargs) class RemoteProcedureBase(object): """Abstract base class that handles all (de)serialization. Child classes must implement the client transport in the __call__ method using the following method signature: :: def __call__(self, *args, **kwargs): :param url: The url for the server endpoint. :param app: The application instance the client belongs to. :param name: The string identifier for the remote method. :param out_header: The header that's going to be sent with the remote call. """ def __init__(self, url, app, name, out_header=None): self.url = url self.app = app initial_ctx = MethodContext(self, MethodContext.CLIENT) initial_ctx.method_request_string = name initial_ctx.out_header = out_header self.contexts = initial_ctx.out_protocol.generate_method_contexts( initial_ctx) def __call__(self, *args, **kwargs): """Serializes its arguments, sends them, receives and deserializes the response and returns it.""" raise NotImplementedError() def get_out_object(self, ctx, args, kwargs): """Serializes the method arguments to output document. :param args: Sequential arguments. :param kwargs: Name-based arguments. """ assert ctx.out_object is None request_raw_class = ctx.descriptor.in_message request_type_info = request_raw_class._type_info request_raw = request_raw_class() for i in range(len(request_type_info)): if i < len(args): setattr(request_raw, request_type_info.keys()[i], args[i]) else: setattr(request_raw, request_type_info.keys()[i], None) for k in request_type_info: if k in kwargs: setattr(request_raw, k, kwargs[k]) ctx.out_object = list(request_raw) def get_out_string(self, ctx): """Serializes the output document to a bytestream.""" assert ctx.out_document is None assert ctx.out_string is None ctx.out_protocol.serialize(ctx, ctx.out_protocol.REQUEST) if ctx.out_error is None: ctx.fire_event('method_return_document') else: ctx.fire_event('method_exception_document') ctx.out_protocol.create_out_string(ctx, string_encoding) if ctx.out_error is None: ctx.fire_event('method_return_string') else: ctx.fire_event('method_exception_string') if ctx.out_string is None: ctx.out_string = [""] def get_in_object(self, ctx): """Deserializes the response bytestream first as a document and then as a native python object. """ assert ctx.in_string is not None assert ctx.in_document is None self.app.in_protocol.create_in_document(ctx) ctx.fire_event('method_accept_document') # sets the ctx.in_body_doc and ctx.in_header_doc properties self.app.in_protocol.decompose_incoming_envelope(ctx, message=self.app.in_protocol.RESPONSE) # this sets ctx.in_object self.app.in_protocol.deserialize(ctx, message=self.app.in_protocol.RESPONSE) type_info = ctx.descriptor.out_message._type_info # TODO: Non-Wrapped Object Support if len(ctx.descriptor.out_message._type_info) == 1: wrapper_attribute = type_info.keys()[0] ctx.in_object = getattr(ctx.in_object, wrapper_attribute, None) class ClientBase(object): """The base class for all client applications. ``self.service`` attribute should be initialized in the constructor of the child class. """ def __init__(self, url, app): self.factory = Factory(app) def set_options(self, **kwargs): """Sets call options. :param out_header: Sets the header object that's going to be sent with the remote procedure call. :param soapheaders: A suds-compatible alias for out_header. """ if ('soapheaders' in kwargs) and ('out_header' in kwargs): raise ValueError('you should specify only one of "soapheaders" or ' '"out_header" keyword arguments.') self.service.out_header = kwargs.get('soapheaders', None) if self.service.out_header is None: self.service.out_header = kwargs.get('out_header', None) spyne-spyne-2.14.0/spyne/client/django.py000066400000000000000000000055471417664205300203430ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The Django client transport for testing Spyne apps the way you'd test Django apps.""" from __future__ import absolute_import from spyne import RemoteService, ClientBase, RemoteProcedureBase from django.test.client import Client class _RemoteProcedure(RemoteProcedureBase): def __init__(self, url, app, name, out_header=None, *args, **kwargs): super(_RemoteProcedure, self).__init__(url, app, name, out_header=out_header) self.secure = kwargs.get('secure', False) def __call__(self, *args, **kwargs): response = self.get_django_response(*args, **kwargs) code = response.status_code self.ctx.in_string = [response.content] # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error elif code >= 400: raise self.ctx.in_error else: return self.ctx.in_object def get_django_response(self, *args, **kwargs): """Return Django ``HttpResponse`` object as RPC result.""" # there's no point in having a client making the same request more than # once, so if there's more than just one context, it is a bug. # the comma-in-assignment trick is a general way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts # sets ctx.out_object self.get_out_object(self.ctx, args, kwargs) # sets ctx.out_string self.get_out_string(self.ctx) out_string = b''.join(self.ctx.out_string) # Hack client = Client() return client.post(self.url, content_type='text/xml', data=out_string, secure=self.secure) class DjangoTestClient(ClientBase): """The Django test client transport.""" def __init__(self, url, app, secure=False): super(DjangoTestClient, self).__init__(url, app) self.service = RemoteService(_RemoteProcedure, url, app, secure=secure) spyne-spyne-2.14.0/spyne/client/http.py000066400000000000000000000047501417664205300200530ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The HTTP (urllib2) client transport.""" from spyne import RemoteService, ClientBase, RemoteProcedureBase from spyne.util.six.moves.urllib.request import Request, urlopen from spyne.util.six.moves.urllib.error import HTTPError class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): # there's no point in having a client making the same request more than # once, so if there's more than just one context, it is a bug. # the comma-in-assignment trick is a general way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts # sets ctx.out_object self.get_out_object(self.ctx, args, kwargs) # sets ctx.out_string self.get_out_string(self.ctx) out_string = b''.join(self.ctx.out_string) # FIXME: just send the iterable to the http stream. request = Request(self.url, out_string) code = 200 try: response = urlopen(request) self.ctx.in_string = [response.read()] except HTTPError as e: code = e.code self.ctx.in_string = [e.read()] # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error elif code >= 400: raise self.ctx.in_error else: return self.ctx.in_object class HttpClient(ClientBase): def __init__(self, url, app): super(HttpClient, self).__init__(url, app) self.service = RemoteService(_RemoteProcedure, url, app) spyne-spyne-2.14.0/spyne/client/twisted/000077500000000000000000000000001417664205300201775ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/client/twisted/__init__.py000066400000000000000000000106761417664205300223220ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The Twisted Http Client transport.""" from spyne import __version__ as VERSION from spyne.util import six from spyne.client import RemoteService from spyne.client import RemoteProcedureBase from spyne.client import ClientBase from zope.interface import implements from twisted.internet import reactor from twisted.internet.defer import Deferred from twisted.internet.protocol import Protocol from twisted.web import error as werror from twisted.web.client import Agent from twisted.web.client import ResponseDone from twisted.web.iweb import IBodyProducer from twisted.web.iweb import UNKNOWN_LENGTH from twisted.web.http_headers import Headers class _Producer(object): if six.PY2: implements(IBodyProducer) _deferred = None def __init__(self, body): """:param body: an iterable of strings""" self.__paused = False # check to see if we can determine the length try: len(body) # iterator? self.length = sum([len(fragment) for fragment in body]) self.body = iter(body) except TypeError: self.length = UNKNOWN_LENGTH self._deferred = Deferred() def startProducing(self, consumer): self.consumer = consumer self.resumeProducing() return self._deferred def resumeProducing(self): self.__paused = False for chunk in self.body: self.consumer.write(chunk) if self.__paused: break else: self._deferred.callback(None) # done producing forever def pauseProducing(self): self.__paused = True def stopProducing(self): self.__paused = True class _Protocol(Protocol): def __init__(self, ctx): self.ctx = ctx self.deferred = Deferred() def dataReceived(self, bytes): self.ctx.in_string.append(bytes) def connectionLost(self, reason): if reason.check(ResponseDone): self.deferred.callback(None) else: self.deferred.errback(reason) class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): # there's no point in having a client making the same request more than # once, so if there's more than just one context, it's rather a bug. # The comma-in-assignment trick is a pedantic way of getting the first # and the only variable from an iterable. so if there's more than one # element in the iterable, it'll fail miserably. self.ctx, = self.contexts self.get_out_object(self.ctx, args, kwargs) self.get_out_string(self.ctx) self.ctx.in_string = [] agent = Agent(reactor) d = agent.request( b'POST', self.url, Headers({b'User-Agent': [b'Spyne Twisted Http Client %s' % VERSION.encode()]}), _Producer(self.ctx.out_string) ) def _process_response(_, response): # this sets ctx.in_error if there's an error, and ctx.in_object if # there's none. self.get_in_object(self.ctx) if self.ctx.in_error is not None: raise self.ctx.in_error elif response.code >= 400: raise werror.Error(response.code) return self.ctx.in_object def _cb_request(response): p = _Protocol(self.ctx) response.deliverBody(p) return p.deferred.addCallback(_process_response, response) return d.addCallback(_cb_request) class TwistedHttpClient(ClientBase): def __init__(self, url, app): super(TwistedHttpClient, self).__init__(url, app) self.service = RemoteService(_RemoteProcedure, url, app) spyne-spyne-2.14.0/spyne/client/zeromq.py000066400000000000000000000032431417664205300204050ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ZeroMQ (zmq.REQ) client transport.""" import zmq from spyne import RemoteService, ClientBase, RemoteProcedureBase context = zmq.Context() class _RemoteProcedure(RemoteProcedureBase): def __call__(self, *args, **kwargs): self.ctx = self.contexts[0] self.get_out_object(self.ctx, args, kwargs) self.get_out_string(self.ctx) out_string = b''.join(self.ctx.out_string) socket = context.socket(zmq.REQ) socket.connect(self.url) socket.send(out_string) self.ctx.in_string = [socket.recv()] self.get_in_object(self.ctx) if not (self.ctx.in_error is None): raise self.ctx.in_error else: return self.ctx.in_object class ZeroMQClient(ClientBase): def __init__(self, url, app): super(ZeroMQClient, self).__init__(url, app) self.service = RemoteService(_RemoteProcedure, url, app) spyne-spyne-2.14.0/spyne/const/000077500000000000000000000000001417664205300163645ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/const/__init__.py000066400000000000000000000053221417664205300204770ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.const`` package contains miscellanous constant values needed in various parts of Spyne.""" MAX_STRING_FIELD_LENGTH = 64 """Maximum length of a string field for :func:`spyne.util.log_repr`""" MAX_ARRAY_ELEMENT_NUM = 2 """Maximum number of array elements for :func:`spyne.util.log_repr`""" MAX_DICT_ELEMENT_NUM = 2 """Maximum number of dict elements for :func:`spyne.util.log_repr`""" MAX_FIELD_NUM = 10 """Maximum number of complex model fields for :func:`spyne.util.log_repr`""" ARRAY_PREFIX = '' """The prefix for Array wrapper objects. You may want to set this to 'ArrayOf' and the ARRAY_SUFFIX to '' for compatibility with some SOAP deployments.""" ARRAY_SUFFIX = 'Array' """The suffix for Array wrapper objects.""" REQUEST_SUFFIX = '' """The suffix for function response objects.""" RESPONSE_SUFFIX = 'Response' """The suffix for function response objects.""" RESULT_SUFFIX = 'Result' """The suffix for function response wrapper objects.""" TYPE_SUFFIX = 'Type' """The suffix for primitives with unnamed constraints.""" PARENT_SUFFIX = 'Parent' """The suffix for parent classes of primitives with unnamed constraints.""" MANDATORY_PREFIX = 'Mandatory' """The prefix for types created with the :func:`spyne.model.Mandatory`.""" MANDATORY_SUFFIX = '' """The suffix for types created with the :func:`spyne.model.Mandatory`.""" DEFAULT_DECLARE_ORDER = 'random' """Order of complex type attrs of :class:`spyne.model.complex.ComplexModel`.""" MIN_GC_INTERVAL = 1.0 """Minimum time in seconds between gc.collect() calls.""" DEFAULT_LOCALE = 'en_US' """Locale code to use for the translation subsystem when locale information is missing in an incoming request.""" WARN_ON_DUPLICATE_FAULTCODE = True """Warn about duplicate faultcodes in all Fault subclasses globally. Only works when CODE class attribute is set for every Fault subclass.""" def add_request_suffix(string): """Concatenates REQUEST_SUFFIX to end of string""" return string + REQUEST_SUFFIX spyne-spyne-2.14.0/spyne/const/ansi_color.py000066400000000000000000000041171417664205300210710ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """You can use the constants in this package to add colour to your logs. You can use the "colorama" package to get ANSI colors working on windows. """ DARK_RED = "" """ANSI colour value for dark red if colours are enabled, empty string otherwise.""" LIGHT_GREEN = "" """ANSI colour value for light green if colours are enabled, empty string otherwise.""" LIGHT_RED = "" """ANSI colour value for light red if colours are enabled, empty string otherwise.""" LIGHT_BLUE = "" """ANSI colour value for light blue if colours are enabled, empty string otherwise.""" END_COLOR = "" """ANSI colour value for end color marker if colours are enabled, empty string otherwise.""" def enable_color(): """Enable colors by setting colour code constants to ANSI color codes.""" global LIGHT_GREEN LIGHT_GREEN = "\033[1;32m" global LIGHT_RED LIGHT_RED = "\033[1;31m" global LIGHT_BLUE LIGHT_BLUE = "\033[1;34m" global DARK_RED DARK_RED = "\033[0;31m" global END_COLOR END_COLOR = "\033[0m" def disable_color(): """Disable colours by setting colour code constants to empty strings.""" global LIGHT_GREEN LIGHT_GREEN = "" global LIGHT_RED LIGHT_RED = "" global LIGHT_BLUE LIGHT_BLUE = "" global DARK_RED DARK_RED = "" global END_COLOR END_COLOR = "" enable_color() spyne-spyne-2.14.0/spyne/const/http.py000066400000000000000000000103541417664205300177200ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.const.http module contains the Http response status codes.""" HTTP_200 = '200 OK' HTTP_201 = '201 Created' HTTP_202 = '202 Accepted' HTTP_203 = '203 Non-Authoritative Information' # (since HTTP/1.1) HTTP_204 = '204 No Content' HTTP_205 = '205 Reset Content' HTTP_206 = '206 Partial Content' HTTP_207 = '207 Multi-Status' # (WebDAV; RFC 4918) HTTP_208 = '208 Already Reported' # (WebDAV; RFC 5842) HTTP_226 = '226 IM Used' # (RFC 3229) HTTP_300 = '300 Multiple Choices' HTTP_301 = '301 Moved Permanently' HTTP_302 = '302 Found' HTTP_303 = '303 See Other' # (since HTTP/1.1) HTTP_304 = '304 Not Modified' HTTP_305 = '305 Use Proxy' # (since HTTP/1.1) HTTP_306 = '306 Switch Proxy' HTTP_307 = '307 Temporary Redirect' # (since HTTP/1.1) HTTP_308 = '308 Permanent Redirect' # (approved as experimental RFC])[11] HTTP_400 = '400 Bad Request' HTTP_401 = '401 Unauthorized' HTTP_402 = '402 Payment Required' HTTP_403 = '403 Forbidden' HTTP_404 = '404 Not Found' HTTP_405 = '405 Method Not Allowed' HTTP_406 = '406 Not Acceptable' HTTP_407 = '407 Proxy Authentication Required' HTTP_408 = '408 Request Timeout' HTTP_409 = '409 Conflict' HTTP_410 = '410 Gone' HTTP_411 = '411 Length Required' HTTP_412 = '412 Precondition Failed' HTTP_413 = '413 Request Entity Too Large' HTTP_414 = '414 Request-URI Too Long' HTTP_415 = '415 Unsupported Media Type' HTTP_416 = '416 Requested Range Not Satisfiable' HTTP_417 = '417 Expectation Failed' HTTP_418 = "418 I'm a teapot" # (RFC 2324) HTTP_420 = '420 Enhance Your Calm' # (Twitter) HTTP_422 = '422 Unprocessable Entity' # (WebDAV; RFC 4918) HTTP_423 = '423 Locked' # (WebDAV; RFC 4918) HTTP_424 = '424 Failed Dependency' # (WebDAV; RFC 4918) HTTP_425 = '425 Unordered Collection' # (Internet draft) HTTP_426 = '426 Upgrade Required' # (RFC 2817) HTTP_428 = '428 Precondition Required' # (RFC 6585) HTTP_429 = '429 Too Many Requests' # (RFC 6585) HTTP_431 = '431 Request Header Fields Too Large' # (RFC 6585) HTTP_444 = '444 No Response' # (Nginx) HTTP_449 = '449 Retry With' # (Microsoft) HTTP_450 = '450 Blocked by Windows Parental Controls' # (Microsoft) HTTP_451 = '451 Unavailable For Legal Reasons' # (Internet draft) HTTP_494 = '494 Request Header Too Large' # (Nginx) HTTP_495 = '495 Cert Error' # (Nginx) HTTP_496 = '496 No Cert' # (Nginx) HTTP_497 = '497 HTTP to HTTPS' # (Nginx) HTTP_499 = '499 Client Closed Request' # (Nginx) HTTP_500 = '500 Internal Server Error' HTTP_501 = '501 Not Implemented' HTTP_502 = '502 Bad Gateway' HTTP_503 = '503 Service Unavailable' HTTP_504 = '504 Gateway Timeout' HTTP_505 = '505 HTTP Version Not Supported' HTTP_506 = '506 Variant Also Negotiates' # (RFC 2295) HTTP_507 = '507 Insufficient Storage' # (WebDAV; RFC 4918) HTTP_508 = '508 Loop Detected' # (WebDAV; RFC 5842) HTTP_509 = '509 Bandwidth Limit Exceeded' # (Apache bw/limited extension) HTTP_510 = '510 Not Extended' # (RFC 2774) HTTP_511 = '511 Network Authentication Required' # (RFC 6585) HTTP_598 = '598 Network read timeout error' # (Unknown) HTTP_599 = '599 Network connect timeout error' # (Unknown) def gen_body_redirect(code, location): from lxml.html.builder import E from lxml.html import tostring return tostring(E.HTML( E.HEAD( E.meta(**{ "http-equiv": "content-type", "content": "text/html;charset=utf-8", }), E.TITLE(code), ), E.BODY( E.H1(code), E.P("The document has moved"), E.A("here", HREF=location), ".", ) )) spyne-spyne-2.14.0/spyne/const/xml.py000066400000000000000000000212111417664205300175330ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.const.xml`` module contains various XML-related constants like namespace prefixes, namespace values and schema uris. """ NS_XML = 'http://www.w3.org/XML/1998/namespace' NS_XSD = 'http://www.w3.org/2001/XMLSchema' NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance' NS_WSA = 'http://schemas.xmlsoap.org/ws/2003/03/addressing' NS_XOP = 'http://www.w3.org/2004/08/xop/include' NS_XHTML = 'http://www.w3.org/1999/xhtml' NS_PLINK = 'http://schemas.xmlsoap.org/ws/2003/05/partner-link/' NS_SOAP11_ENC = 'http://schemas.xmlsoap.org/soap/encoding/' NS_SOAP11_ENV = 'http://schemas.xmlsoap.org/soap/envelope/' NS_SOAP12_ENC = 'http://www.w3.org/2003/05/soap-encoding' NS_SOAP12_ENV = 'http://www.w3.org/2003/05/soap-envelope' NS_WSDL11 = 'http://schemas.xmlsoap.org/wsdl/' NS_WSDL11_SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/' NS_WSDL11_SOAP12 = 'http://schemas.xmlsoap.org/wsdl/soap12/' NS_WSDL11_HTTP = 'http://schemas.xmlsoap.org/wsdl/http/' NSMAP = { 'xml': NS_XML, 'xs': NS_XSD, 'xsi': NS_XSI, 'plink': NS_PLINK, 'wsdlsoap11': NS_WSDL11_SOAP, 'wsdlsoap12': NS_WSDL11_SOAP12, 'wsdl': NS_WSDL11, 'soap11enc': NS_SOAP11_ENC, 'soap11env': NS_SOAP11_ENV, 'soap12env': NS_SOAP12_ENV, 'soap12enc': NS_SOAP12_ENC, 'wsa': NS_WSA, 'xop': NS_XOP, 'http': NS_WSDL11_HTTP, } PREFMAP = None def _regen_prefmap(): global PREFMAP PREFMAP = dict([(b, a) for a, b in NSMAP.items()]) _regen_prefmap() schema_location = { NS_XSD: 'http://www.w3.org/2001/XMLSchema.xsd', } class DEFAULT_NS(object): pass def get_binding_ns(protocol_type): "Returns the wsdl binding namespace based on the protocol type" if 'soap12' in protocol_type: return WSDL11_SOAP12 elif 'http' in protocol_type: return WSDL11_HTTP else: # Bind to Soap1.1 namespace by default for backwards compatibility return WSDL11_SOAP def Tnswrap(ns): return lambda s: "{%s}%s" % (ns, s) XML = Tnswrap(NS_XML) XSD = Tnswrap(NS_XSD) XSI = Tnswrap(NS_XSI) WSA = Tnswrap(NS_WSA) XOP = Tnswrap(NS_XOP) XHTML = Tnswrap(NS_XHTML) PLINK = Tnswrap(NS_PLINK) SOAP11_ENC = Tnswrap(NS_SOAP11_ENC) SOAP11_ENV = Tnswrap(NS_SOAP11_ENV) SOAP12_ENC = Tnswrap(NS_SOAP12_ENC) SOAP12_ENV = Tnswrap(NS_SOAP12_ENV) WSDL11 = Tnswrap(NS_WSDL11) WSDL11_SOAP = Tnswrap(NS_WSDL11_SOAP) WSDL11_SOAP12 = Tnswrap(NS_WSDL11_SOAP12) WSDL11_HTTP = Tnswrap(NS_WSDL11_HTTP) # names starting with underscore need () around to be used as proper regexps _PATT_BASE_CHAR = \ u"[\u0041-\u005A]|[\u0061-\u007A]|[\u00C0-\u00D6]|[\u00D8-\u00F6]" \ u"|[\u00F8-\u00FF]|[\u0100-\u0131]|[\u0134-\u013E]|[\u0141-\u0148]" \ u"|[\u014A-\u017E]|[\u0180-\u01C3]|[\u01CD-\u01F0]|[\u01F4-\u01F5]" \ u"|[\u01FA-\u0217]|[\u0250-\u02A8]|[\u02BB-\u02C1]|\u0386|[\u0388-\u038A]" \ u"|\u038C|[\u038E-\u03A1]|[\u03A3-\u03CE]|[\u03D0-\u03D6]" \ u"|\u03DA|\u03DC|\u03DE|\u03E0|[\u03E2-\u03F3]|[\u0401-\u040C]" \ u"|[\u040E-\u044F]|[\u0451-\u045C]|[\u045E-\u0481]|[\u0490-\u04C4]" \ u"|[\u04C7-\u04C8]|[\u04CB-\u04CC]|[\u04D0-\u04EB]|[\u04EE-\u04F5]" \ u"|[\u04F8-\u04F9]|[\u0531-\u0556]|\u0559|[\u0561-\u0586]|[\u05D0-\u05EA]" \ u"|[\u05F0-\u05F2]|[\u0621-\u063A]|[\u0641-\u064A]|[\u0671-\u06B7]" \ u"|[\u06BA-\u06BE]|[\u06C0-\u06CE]|[\u06D0-\u06D3]|\u06D5|[\u06E5-\u06E6]" \ u"|[\u0905-\u0939]|\u093D|[\u0958-\u0961]|[\u0985-\u098C]|[\u098F-\u0990]" \ u"|[\u0993-\u09A8]|[\u09AA-\u09B0]|\u09B2|[\u09B6-\u09B9]|[\u09DC-\u09DD]" \ u"|[\u09DF-\u09E1]|[\u09F0-\u09F1]|[\u0A05-\u0A0A]|[\u0A0F-\u0A10]" \ u"|[\u0A13-\u0A28]|[\u0A2A-\u0A30]|[\u0A32-\u0A33]|[\u0A35-\u0A36]" \ u"|[\u0A38-\u0A39]|[\u0A59-\u0A5C]|\u0A5E|[\u0A72-\u0A74]|[\u0A85-\u0A8B]" \ u"|\u0A8D|[\u0A8F-\u0A91]|[\u0A93-\u0AA8]|[\u0AAA-\u0AB0]|[\u0AB2-\u0AB3]" \ u"|[\u0AB5-\u0AB9]|\u0ABD|\u0AE0|[\u0B05-\u0B0C]|[\u0B0F-\u0B10]" \ u"|[\u0B13-\u0B28]|[\u0B2A-\u0B30]|[\u0B32-\u0B33]|[\u0B36-\u0B39]|\u0B3D" \ u"|[\u0B5C-\u0B5D]|[\u0B5F-\u0B61]|[\u0B85-\u0B8A]|[\u0B8E-\u0B90]" \ u"|[\u0B92-\u0B95]|[\u0B99-\u0B9A]|\u0B9C|[\u0B9E-\u0B9F]|[\u0BA3-\u0BA4]" \ u"|[\u0BA8-\u0BAA]|[\u0BAE-\u0BB5]|[\u0BB7-\u0BB9]|[\u0C05-\u0C0C]" \ u"|[\u0C0E-\u0C10]|[\u0C12-\u0C28]|[\u0C2A-\u0C33]|[\u0C35-\u0C39]" \ u"|[\u0C60-\u0C61]|[\u0C85-\u0C8C]|[\u0C8E-\u0C90]|[\u0C92-\u0CA8]" \ u"|[\u0CAA-\u0CB3]|[\u0CB5-\u0CB9]|\u0CDE|[\u0CE0-\u0CE1]|[\u0D05-\u0D0C]" \ u"|[\u0D0E-\u0D10]|[\u0D12-\u0D28]|[\u0D2A-\u0D39]|[\u0D60-\u0D61]" \ u"|[\u0E01-\u0E2E]|\u0E30|[\u0E32-\u0E33]|[\u0E40-\u0E45]|[\u0E81-\u0E82]" \ u"|\u0E84|[\u0E87-\u0E88]|\u0E8A|\u0E8D|[\u0E94-\u0E97]|[\u0E99-\u0E9F]" \ u"|[\u0EA1-\u0EA3]|\u0EA5|\u0EA7|[\u0EAA-\u0EAB]|[\u0EAD-\u0EAE]|\u0EB0" \ u"|[\u0EB2-\u0EB3]|\u0EBD|[\u0EC0-\u0EC4]|[\u0F40-\u0F47]|[\u0F49-\u0F69]" \ u"|[\u10A0-\u10C5]|[\u10D0-\u10F6]|\u1100|[\u1102-\u1103]|[\u1105-\u1107]" \ u"|\u1109|[\u110B-\u110C]|[\u110E-\u1112]|\u113C|\u113E|\u1140|\u114C" \ u"|\u114E|\u1150|[\u1154-\u1155]|\u1159|[\u115F-\u1161]|\u1163|\u1165" \ u"|\u1167|\u1169|[\u116D-\u116E]|[\u1172-\u1173]|\u1175|\u119E|\u11A8" \ u"|\u11AB|[\u11AE-\u11AF]|[\u11B7-\u11B8]|\u11BA|[\u11BC-\u11C2]|\u11EB" \ u"|\u11F0|\u11F9|[\u1E00-\u1E9B]|[\u1EA0-\u1EF9]|[\u1F00-\u1F15]" \ u"|[\u1F18-\u1F1D]|[\u1F20-\u1F45]|[\u1F48-\u1F4D]|[\u1F50-\u1F57]|\u1F59" \ u"|\u1F5B|\u1F5D|[\u1F5F-\u1F7D]|[\u1F80-\u1FB4]|[\u1FB6-\u1FBC]|\u1FBE" \ u"|[\u1FC2-\u1FC4]|[\u1FC6-\u1FCC]|[\u1FD0-\u1FD3]|[\u1FD6-\u1FDB]" \ u"|[\u1FE0-\u1FEC]|[\u1FF2-\u1FF4]|[\u1FF6-\u1FFC]|\u2126|[\u212A-\u212B]" \ u"|\u212E|[\u2180-\u2182]|[\u3041-\u3094]|[\u30A1-\u30FA]|[\u3105-\u312C]" \ u"|[\uAC00-\uD7A3]" _PATT_IDEOGRAPHIC = u"[\u4E00-\u9FA5]|\u3007|[\u3021-\u3029]" _PATT_COMBINING_CHAR = u"[\u0300-\u0345]|[\u0360-\u0361]|[\u0483-\u0486]" \ u"|[\u0591-\u05A1]|[\u05A3-\u05B9]|[\u05BB-\u05BD]|\u05BF|[\u05C1-\u05C2]" \ u"|\u05C4|[\u064B-\u0652]|\u0670|[\u06D6-\u06DC]|[\u06DD-\u06DF]" \ u"|[\u06E0-\u06E4]|[\u06E7-\u06E8]|[\u06EA-\u06ED]|[\u0901-\u0903]|\u093C" \ u"|[\u093E-\u094C]|\u094D|[\u0951-\u0954]|[\u0962-\u0963]|[\u0981-\u0983]" \ u"|\u09BC|\u09BE|\u09BF|[\u09C0-\u09C4]|[\u09C7-\u09C8]|[\u09CB-\u09CD]" \ u"|\u09D7|[\u09E2-\u09E3]|\u0A02|\u0A3C|\u0A3E|\u0A3F|[\u0A40-\u0A42]" \ u"|[\u0A47-\u0A48]|[\u0A4B-\u0A4D]|[\u0A70-\u0A71]|[\u0A81-\u0A83]|\u0ABC" \ u"|[\u0ABE-\u0AC5]|[\u0AC7-\u0AC9]|[\u0ACB-\u0ACD]|[\u0B01-\u0B03]|\u0B3C" \ u"|[\u0B3E-\u0B43]|[\u0B47-\u0B48]|[\u0B4B-\u0B4D]|[\u0B56-\u0B57]" \ u"|[\u0B82-\u0B83]|[\u0BBE-\u0BC2]|[\u0BC6-\u0BC8]|[\u0BCA-\u0BCD]|\u0BD7" \ u"|[\u0C01-\u0C03]|[\u0C3E-\u0C44]|[\u0C46-\u0C48]|[\u0C4A-\u0C4D]" \ u"|[\u0C55-\u0C56]|[\u0C82-\u0C83]|[\u0CBE-\u0CC4]|[\u0CC6-\u0CC8]" \ u"|[\u0CCA-\u0CCD]|[\u0CD5-\u0CD6]|[\u0D02-\u0D03]|[\u0D3E-\u0D43]" \ u"|[\u0D46-\u0D48]|[\u0D4A-\u0D4D]|\u0D57|\u0E31|[\u0E34-\u0E3A]" \ u"|[\u0E47-\u0E4E]|\u0EB1|[\u0EB4-\u0EB9]|[\u0EBB-\u0EBC]|[\u0EC8-\u0ECD]" \ u"|[\u0F18-\u0F19]|\u0F35|\u0F37|\u0F39|\u0F3E|\u0F3F|[\u0F71-\u0F84]" \ u"|[\u0F86-\u0F8B]|[\u0F90-\u0F95]|\u0F97|[\u0F99-\u0FAD]|[\u0FB1-\u0FB7]" \ u"|\u0FB9|[\u20D0-\u20DC]|\u20E1|[\u302A-\u302F]|\u3099|\u309A" _PATT_DIGIT = u"[\u0030-\u0039]|[\u0660-\u0669]|[\u06F0-\u06F9]|[\u0966-\u096F]" \ u"|[\u09E6-\u09EF]|[\u0A66-\u0A6F]|[\u0AE6-\u0AEF]|[\u0B66-\u0B6F]" \ u"|[\u0BE7-\u0BEF]|[\u0C66-\u0C6F]|[\u0CE6-\u0CEF]|[\u0D66-\u0D6F]" \ u"|[\u0E50-\u0E59]|[\u0ED0-\u0ED9]|[\u0F20-\u0F29]" _PATT_EXTENDER = u"\u00B7|\u02D0|\u02D1|\u0387|\u0640|\u0E46|\u0EC6|\u3005" \ u"|[\u3031-\u3035]|[\u309D-\u309E]|[\u30FC-\u30FE]" PATT_LETTER = u"(%s)" % u'|'.join([_PATT_BASE_CHAR, _PATT_IDEOGRAPHIC]) PATT_NAMECHAR = u"(%s)" % u'|'.join([PATT_LETTER, _PATT_DIGIT, u'.', u'-', u'_', u':', _PATT_COMBINING_CHAR, _PATT_EXTENDER]) PATT_NAME = u"(%s)(%s)+" % (u'|'.join([PATT_LETTER, u'_', u':']), u"(%s)*" % PATT_NAMECHAR) PATT_NMTOKEN = u"(%s)+" % PATT_NAMECHAR spyne-spyne-2.14.0/spyne/const/xml_ns.py000066400000000000000000000037351417664205300202460ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # This module is DEPRECATED. Use ``spyne.const.xml``. xml = 'http://www.w3.org/XML/1998/namespace' xsd = 'http://www.w3.org/2001/XMLSchema' xsi = 'http://www.w3.org/2001/XMLSchema-instance' wsa = 'http://schemas.xmlsoap.org/ws/2003/03/addressing' xop = 'http://www.w3.org/2004/08/xop/include' soap = 'http://schemas.xmlsoap.org/wsdl/soap/' wsdl = 'http://schemas.xmlsoap.org/wsdl/' xhtml = 'http://www.w3.org/1999/xhtml' plink = 'http://schemas.xmlsoap.org/ws/2003/05/partner-link/' soap11_enc = 'http://schemas.xmlsoap.org/soap/encoding/' soap11_env = 'http://schemas.xmlsoap.org/soap/envelope/' soap12_env = 'http://www.w3.org/2003/05/soap-envelope' soap12_enc = 'http://www.w3.org/2003/05/soap-encoding' const_nsmap = { 'xml': xml, 'xs': xsd, 'xsi': xsi, 'plink': plink, 'soap': soap, 'wsdl': wsdl, 'soap11enc': soap11_enc, 'soap11env': soap11_env, 'soap12env': soap12_env, 'soap12enc': soap12_enc, 'wsa': wsa, 'xop': xop, } const_prefmap = None def regen_prefmap(): global const_prefmap const_prefmap = dict([(b, a) for a, b in const_nsmap.items()]) regen_prefmap() schema_location = { xsd: 'http://www.w3.org/2001/XMLSchema.xsd', } class DEFAULT_NS(object): pass spyne-spyne-2.14.0/spyne/context.py000066400000000000000000000365431417664205300173070ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import gc, logging logger = logging.getLogger('spyne') from time import time from copy import copy from collections import deque, defaultdict from spyne import const _LAST_GC_RUN = 0.0 class AuxMethodContext(object): """Generic object that holds information specific to auxiliary methods""" def __init__(self, parent, error): self.parent = parent """Primary context that this method was bound to.""" self.error = error """Error from primary context (if any).""" class TransportContext(object): """Generic object that holds transport-specific context information""" def __init__(self, parent, transport, type=None): self.parent = parent """The MethodContext this object belongs to""" self.itself = transport """The transport itself; i.e. a ServerBase instance.""" self.type = type """The protocol the transport uses.""" self.app = transport.app self.request_encoding = None """General purpose variable to hold the string identifier of a request encoding. It's nowadays usually 'utf-8', especially with http data""" self.remote_addr = None """The address of the other end of the connection.""" self.sessid = '' """The session id.""" def get_peer(self): """Returns None when not applicable, otherwise returns :class:`spyne.Address`""" return None class ProtocolContext(object): """Generic object that holds protocol-specific context information""" def __init__(self, parent, transport, type=None): self.parent = parent """The MethodContext this object belongs to""" self.itself = transport """The protocol itself as passed to the `Application` init. This is a `ProtocolBase` instance.""" self.type = type """The protocol the transport uses.""" self._subctx = defaultdict( lambda: self.__class__(parent, transport, type)) def __getitem__(self, item): return self._subctx[item] class EventContext(object): """Generic object that holds event-specific context information""" def __init__(self, parent, event_id=None): self.parent = parent self.event_id = event_id class MethodContext(object): """The base class for all RPC Contexts. Holds all information about the current state of execution of a remote procedure call. """ SERVER = type("SERVER", (object,), {}) CLIENT = type("CLIENT", (object,), {}) TransportContext = TransportContext frozen = False def copy(self): retval = copy(self) if retval.transport is not None: retval.transport.parent = retval if retval.inprot_ctx is not None: retval.inprot_ctx.parent = retval if retval.outprot_ctx is not None: retval.outprot_ctx.parent = retval if retval.event is not None: retval.event.parent = retval if retval.aux is not None: retval.aux.parent = retval return retval def fire_event(self, event, *args, **kwargs): self.app.event_manager.fire_event(event, self, *args, **kwargs) desc = self.descriptor if desc is not None: for evmgr in desc.event_managers: evmgr.fire_event(event, self, *args, **kwargs) @property def method_name(self): """The public name of the method the ``method_request_string`` was matched to. """ if self.descriptor is None: return None else: return self.descriptor.name def __init__(self, transport, way): # metadata self.call_start = time() """The time the rpc operation was initiated in seconds-since-epoch format. Useful for benchmarking purposes.""" self.call_end = None """The time the rpc operation was completed in seconds-since-epoch format. Useful for benchmarking purposes.""" self.is_closed = False """`True` means response is fully sent and request finalized.""" self.app = transport.app """The parent application.""" self.udc = None """The user defined context. Use it to your liking.""" self.transport = None """The transport-specific context. Transport implementors can use this to their liking.""" if self.TransportContext is not None: self.transport = self.TransportContext(self, transport) self.outprot_ctx = None """The output-protocol-specific context. Protocol implementors can use this to their liking.""" if self.app.out_protocol is not None: self.outprot_ctx = self.app.out_protocol.get_context(self, transport) self.inprot_ctx = None """The input-protocol-specific context. Protocol implementors can use this to their liking.""" if self.app.in_protocol is not None: self.inprot_ctx = self.app.in_protocol.get_context(self, transport) self.protocol = None """The protocol-specific context. This points to the in_protocol when an incoming message is being processed and out_protocol when an outgoing message is being processed.""" if way is MethodContext.SERVER: self.protocol = self.inprot_ctx elif way is MethodContext.CLIENT: self.protocol = self.outprot_ctx else: raise ValueError(way) self.event = EventContext(self) """Event-specific context. Use this as you want, preferably only in events, as you'd probably want to separate the event data from the method data.""" self.aux = None """Auxiliary-method specific context. You can use this to share data between auxiliary sessions. This is not set in primary contexts. """ self.method_request_string = None """This is used to decide which native method to call. It is set by the protocol classes.""" self.files = [] """List of stuff to be closed when closing this context. Anything that has a close() callable can go in.""" self.active = False """Transports may choose to delay incoming requests. When a context is queued but waiting, this is False.""" self.__descriptor = None # # Input # # stream self.in_string = None """Incoming bytestream as a sequence of ``str`` or ``bytes`` instances.""" # parsed self.in_document = None """Incoming document, what you get when you parse the incoming stream.""" self.in_header_doc = None """Incoming header document of the request.""" self.in_body_doc = None """Incoming body document of the request.""" # native self.in_error = None """Native python error object. If this is set, either there was a parsing error or the incoming document was representing an exception. """ self.in_header = None """Deserialized incoming header -- a native object.""" self.in_object = None """In the request (i.e. server) case, this contains the function argument sequence for the function in the service definition class. In the response (i.e. client) case, this contains the object returned by the remote procedure call. It's always a sequence of objects: * ``[None]`` when the function has no output (client)/input (server) types. * A single-element list that wraps the return value when the function has one return type defined, * A tuple of return values in case of the function having more than one return value. The order of the argument sequence is in line with ``self.descriptor.in_message._type_info.keys()``. """ # # Output # # native self.out_object = None """In the response (i.e. server) case, this contains the native python object(s) returned by the function in the service definition class. In the request (i.e. client) case, this contains the function arguments passed to the function call wrapper. It's always a sequence of objects: * ``[None]`` when the function has no output (server)/input (client) types. * A single-element list that wraps the return value when the function has one return type defined, * A tuple of return values in case of the function having more than one return value. The order of the argument sequence is in line with ``self.descriptor.out_message._type_info.keys()``. """ self.out_header = None """Native python object set by the function in the service definition class.""" self.out_error = None """Native exception thrown by the function in the service definition class.""" # parsed self.out_body_doc = None """Serialized body object.""" self.out_header_doc = None """Serialized header object.""" self.out_document = None """out_body_doc and out_header_doc wrapped in the outgoing envelope""" # stream self.out_string = None """The pull interface to the outgoing bytestream. It's a sequence of strings (which could also be a generator).""" self.out_stream = None """The push interface to the outgoing bytestream. It's a file-like object.""" self.function = None """The callable of the user code.""" self.locale = None """The locale the request will use when needed for things like date formatting, html rendering and such.""" self._in_protocol = transport.app.in_protocol """The protocol that will be used to (de)serialize incoming input""" self._out_protocol = transport.app.out_protocol """The protocol that will be used to (de)serialize outgoing input""" self.pusher_stack = [] """Last one is the current PushBase instance writing to the stream.""" self.frozen = True """When this is set, no new attribute can be added to this class instance. This is mostly for internal use. """ self.fire_event("method_context_created") def get_descriptor(self): return self.__descriptor def set_descriptor(self, descriptor): self.__descriptor = descriptor self.function = descriptor.function descriptor = property(get_descriptor, set_descriptor) """The :class:``MethodDescriptor`` object representing the current method. It is only set when the incoming request was successfully mapped to a method in the public interface. The contents of this property should not be changed by the user code. """ # FIXME: Deprecated. Use self.descriptor.service_class. @property def service_class(self): if self.descriptor is not None: return self.descriptor.service_class def __setattr__(self, k, v): if not self.frozen or k in self.__dict__ or k in \ ('descriptor', 'out_protocol'): object.__setattr__(self, k, v) else: raise ValueError("use the udc member for storing arbitrary data " "in the method context") def __repr__(self): retval = deque() for k, v in self.__dict__.items(): if isinstance(v, dict): ret = deque(['{']) for k2, v2 in sorted(v.items()): ret.append('\t\t%r: %r,' % (k2, v2)) ret.append('\t}') ret = '\n'.join(ret) retval.append("\n\t%s=%s" % (k, ret)) else: retval.append("\n\t%s=%r" % (k, v)) retval.append('\n)') return ''.join((self.__class__.__name__, '(', ', '.join(retval), ')')) def close(self): global _LAST_GC_RUN self.call_end = time() self.app.event_manager.fire_event("method_context_closed", self) for f in self.files: f.close() self.is_closed = True # this is important to have file descriptors returned in a timely manner t = time() if (t - _LAST_GC_RUN) > const.MIN_GC_INTERVAL: gc.collect() dt = (time() - t) _LAST_GC_RUN = t logger.debug("gc.collect() took around %dms.", round(dt, 2) * 1000) def set_out_protocol(self, what): self._out_protocol = what if self._out_protocol.app is None: self._out_protocol.set_app(self.app) def get_out_protocol(self): return self._out_protocol out_protocol = property(get_out_protocol, set_out_protocol) def set_in_protocol(self, what): self._in_protocol = what self._in_protocol.app = self.app def get_in_protocol(self): return self._in_protocol in_protocol = property(get_in_protocol, set_in_protocol) class FakeContext(object): def __init__(self, app=None, descriptor=None, in_header=None, in_object=None, in_error=None, in_document=None, in_string=None, out_object=None, out_error=None, out_document=None, out_string=None, in_protocol=None, out_protocol=None): self.app = app self.descriptor = descriptor self.in_header = in_header self.in_object = in_object self.in_error = in_error self.in_document = in_document self.in_string = in_string self.out_error = out_error self.out_object = out_object self.out_document = out_document self.out_string = out_string self.in_protocol = in_protocol self.out_protocol = out_protocol if self.in_protocol is not None: self.inprot_ctx = self.in_protocol.get_context(self, None) else: self.inprot_ctx = type("ProtocolContext", (object,), {})() from spyne.protocol.html._base import HtmlClothProtocolContext if self.out_protocol is not None: self.outprot_ctx = self.out_protocol.get_context(self, None) else: # The outprot_ctx here must contain properties from ALL tested # protocols' context objects. That's why we use # HtmlClothProtocolContext here, it's just the one with most # attributes. self.outprot_ctx = HtmlClothProtocolContext(self, None) self.protocol = self.outprot_ctx self.transport = type("ProtocolContext", (object,), {})() spyne-spyne-2.14.0/spyne/decorator.py000066400000000000000000000563661417664205300176120ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.decorator`` module contains the the @srpc decorator and its helper methods. The @srpc decorator is responsible for tagging methods as remote procedure calls extracting method's input and output types. It's possible to create custom decorators that wrap the @srpc decorator in order to have a more elegant way of passing frequently-used parameter values. The @rpc decorator is a simple example of this. """ import spyne.const.xml from copy import copy from inspect import isclass from spyne import MethodDescriptor # Empty means empty input, bare output. Doesn't say anything about response # being empty from spyne import LogicError from spyne import BODY_STYLE_EMPTY from spyne import BODY_STYLE_WRAPPED from spyne import BODY_STYLE_BARE from spyne import BODY_STYLE_OUT_BARE from spyne import BODY_STYLE_EMPTY_OUT_BARE from spyne.model import ModelBase, ComplexModel, ComplexModelBase from spyne.model.complex import TypeInfo, recust_selfref, SelfReference from spyne.const import add_request_suffix def _produce_input_message(f, params, in_message_name, in_variable_names, no_ctx, no_self, argnames, body_style_str, self_ref_cls, in_wsdl_part_name): arg_start = 0 if no_ctx is False: arg_start += 1 if no_self is False: arg_start += 1 if argnames is None: try: argcount = f.__code__.co_argcount argnames = f.__code__.co_varnames[arg_start:argcount] except AttributeError: raise TypeError( "It's not possible to instrospect builtins. You must pass a " "sequence of argument names as the '_args' argument to the " "rpc decorator to manually denote the arguments that this " "function accepts." ) if no_self is False: params = [self_ref_cls.novalidate_freq()] + params argnames = ('self',) + argnames if len(params) != len(argnames): raise LogicError("%r function has %d argument(s) but its decorator " "has %d." % (f.__name__, len(argnames), len(params))) else: argnames = copy(argnames) if len(params) != len(argnames): raise LogicError("%r function has %d argument(s) but the _args " "argument has %d." % ( f.__name__, len(argnames), len(params))) in_params = TypeInfo() from spyne import SelfReference for k, v in zip(argnames, params): try: is_self_ref = issubclass(v, SelfReference) except TypeError: is_self_ref = False if is_self_ref: if no_self is False: raise LogicError("SelfReference can't be used in @rpc") v = recust_selfref(v, self_ref_cls) k = in_variable_names.get(k, k) in_params[k] = v ns = spyne.const.xml.DEFAULT_NS if in_message_name.startswith("{"): ns, _, in_message_name = in_message_name[1:].partition("}") message = None if body_style_str == 'bare': if len(in_params) > 1: # The soap Body elt contains 1 elt (called "body entry" in the soap # standard) per method call. If bare methods were allowed to have >1 # argument, it would have to be serialized as multiple body entries, # which would violate the standard. It's easy to work around this # restriction by creating a ComplexModel that contains all the # required parameters. raise LogicError("body_style='bare' can handle at most one " "function argument.") if len(in_params) == 0: message = ComplexModel.produce(type_name=in_message_name, namespace=ns, members=in_params) else: message, = in_params.values() message = message.customize(sub_name=in_message_name, sub_ns=ns) if issubclass(message, ComplexModelBase) and not message._type_info: raise LogicError("body_style='bare' does not allow empty " "model as param") # there can't be multiple arguments here. if message.__type_name__ is ModelBase.Empty: message._fill_empty_type_name(ns, in_message_name, "%s_arg0" % in_message_name) else: message = ComplexModel.produce(type_name=in_message_name, namespace=ns, members=in_params) message.__namespace__ = ns if in_wsdl_part_name: message = message.customize(wsdl_part_name=in_wsdl_part_name) return message def _validate_body_style(kparams): _body_style = kparams.pop('_body_style', None) _soap_body_style = kparams.pop('_soap_body_style', None) allowed_body_styles = ('wrapped', 'bare', 'out_bare') if _body_style is None: _body_style = 'wrapped' elif not (_body_style in allowed_body_styles): raise ValueError("body_style must be one of %r" % (allowed_body_styles,)) elif _soap_body_style == 'document': _body_style = 'wrapped' elif _soap_body_style == 'rpc': _body_style = 'bare' elif _soap_body_style is None: pass else: raise ValueError("soap_body_style must be one of ('rpc', 'document')") assert _body_style in ('wrapped', 'bare', 'out_bare') return _body_style def _produce_output_message(func_name, body_style_str, self_ref_cls, no_self, kparams): """Generate an output message for "rpc"-style API methods. This message is a wrapper to the declared return type. """ _returns = kparams.pop('_returns', None) try: is_self_ref = issubclass(_returns, SelfReference) except TypeError: is_self_ref = False if is_self_ref: if no_self is False: raise LogicError("SelfReference can't be used in @rpc") _returns = recust_selfref(_returns, self_ref_cls) _is_out_message_name_overridden = not ('_out_message_name' in kparams) _out_message_name = kparams.pop('_out_message_name', '%s%s' % (func_name, spyne.const.RESPONSE_SUFFIX)) if no_self is False and \ (body_style_str == 'wrapped' or _is_out_message_name_overridden): _out_message_name = '%s.%s' % \ (self_ref_cls.get_type_name(), _out_message_name) _out_wsdl_part_name = kparams.pop('_wsdl_part_name', None) out_params = TypeInfo() if _returns and body_style_str == 'wrapped': if isinstance(_returns, (list, tuple)): default_names = ['%s%s%d'% (func_name, spyne.const.RESULT_SUFFIX, i) for i in range(len(_returns))] _out_variable_names = kparams.pop('_out_variable_names', default_names) assert (len(_returns) == len(_out_variable_names)) var_pair = zip(_out_variable_names, _returns) out_params = TypeInfo(var_pair) else: _out_variable_name = kparams.pop('_out_variable_name', '%s%s' % (func_name, spyne.const.RESULT_SUFFIX)) out_params[_out_variable_name] = _returns ns = spyne.const.xml.DEFAULT_NS if _out_message_name.startswith("{"): _out_message_name_parts = _out_message_name[1:].partition("}") ns = _out_message_name_parts[0] # skip index 1, it is the closing '}' _out_message_name = _out_message_name_parts[2] if body_style_str.endswith('bare') and _returns is not None: message = _returns.customize(sub_name=_out_message_name, sub_ns=ns) if message.__type_name__ is ModelBase.Empty: message.__type_name__ = _out_message_name else: message = ComplexModel.produce(type_name=_out_message_name, namespace=ns, members=out_params) message.Attributes._wrapper = True message.__namespace__ = ns # FIXME: is this necessary? if _out_wsdl_part_name: message = message.customize(wsdl_part_name=_out_wsdl_part_name) return message def _substitute_self_reference(params, kparams, self_ref_replacement, _no_self): from spyne.model import SelfReference for i, v in enumerate(params): if isclass(v) and issubclass(v, SelfReference): if _no_self: raise LogicError("SelfReference can't be used in @rpc") params[i] = recust_selfref(v, self_ref_replacement) else: params[i] = v for k, v in kparams.items(): if isclass(v) and issubclass(v, SelfReference): if _no_self: raise LogicError("SelfReference can't be used in @rpc") kparams[k] = recust_selfref(v, self_ref_replacement) else: kparams[k] = v def _get_event_managers(kparams): _evmgr = kparams.pop("_evmgr", None) _evmgrs = kparams.pop("_evmgrs", None) if _evmgr is not None and _evmgrs is not None: raise LogicError("Pass one of _evmgr or _evmgrs but not both") if _evmgr is not None: _evmgrs = [_evmgr] _event_manager = kparams.pop("_event_manager", None) _event_managers = kparams.pop("_event_managers", None) if _event_manager is not None and _event_managers is not None: raise LogicError("Pass one of _event_manager or " "_event_managers but not both") if _event_manager is not None: _event_managers = [_event_manager] if _evmgrs is not None and _event_managers is not None: raise LogicError("You must pass at most one of _evmgr* " "arguments or _event_manager* arguments") elif _evmgrs is not None: _event_managers = _evmgrs return _event_managers if _event_managers is not None else [] def rpc(*params, **kparams): """Method decorator to tag a method as a remote procedure call in a :class:`spyne.service.Service` subclass. You should use the :class:`spyne.server.null.NullServer` transport if you want to call the methods directly. You can also use the 'function' attribute of the returned object to call the function itself. ``_operation_name`` vs ``_in_message_name``: Soap clients(SoapUI, Savon, suds) will use the operation name as the function name. The name of the input message(_in_message_name) is irrelevant when interfacing in this manner; this is because the clients mostly wrap around it. However, the soap xml request only uses the input message when posting with the soap server; the other protocols only use the input message as well. ``_operation_name`` cannot be used with ``_in_message_name``. :param _returns: Denotes The return type of the function. It can be a type or a sequence of types for functions that have multiple return values. :param _in_header: A type or an iterable of types that that this method accepts as incoming header. :param _out_header: A type or an iterable of types that that this method sends as outgoing header. :param _operation_name: The function's soap operation name. The operation and SoapAction names will be equal to the value of ``_operation_name``. Default is the function name. :param _in_message_name: The public name of the function's input message. Default is: ``_operation_name + REQUEST_SUFFIX``. :param _out_message_name: The public name of the function's output message. Default is: ``_operation_name + RESPONSE_SUFFIX``. :param _in_arg_names: The public names of the function arguments. It's a dict that maps argument names in the code to public ones. :param _in_variable_names: **DEPRECATED** Same as _in_arg_names, kept for backwards compatibility. :param _out_variable_name: The public name of the function response object. It's a string. Ignored when ``_body_style != 'wrapped'`` or ``_returns`` is a sequence. :param _out_variable_names: The public name of the function response object. It's a sequence of strings. Ignored when ``_body_style != 'wrapped'`` or or ``_returns`` is not a sequence. Must be the same length as ``_returns``. :param _body_style: One of ``('bare', 'wrapped')``. Default: ``'wrapped'``. In wrapped mode, wraps response objects in an additional class. :param _soap_body_style: One of ('rpc', 'document'). Default ``'document'``. ``_soap_body_style='document'`` is an alias for ``_body_style='wrapped'``. ``_soap_body_style='rpc'`` is an alias for ``_body_style='bare'``. :param _port_type: Soap port type string. :param _no_ctx: Don't pass implicit ctx object to the user method. :param _no_self: This method does not get an implicit 'self' argument (before any other argument, including ctx). :param _udd: Short for User Defined Data, you can use this to mark the method with arbitrary metadata. :param _udp: **DEPRECATED** synonym of ``_udd``. :param _aux: The auxiliary backend to run this method. ``None`` if primary. :param _throws: A sequence of exceptions that this function can throw. This has no real functionality besides publishing this information in interface documents. :param _args: the name of the arguments to expose. :param _event_managers: An iterable of :class:`spyne.EventManager` instances. This is useful for adding additional event handlers to individual functions. :param _event_manager: An instance of :class:`spyne.EventManager` class. :param _logged: May be the string '...' to denote that the rpc arguments will not be logged. :param _evmgrs: Same as ``_event_managers``. :param _evmgr: Same as ``_event_manager``. :param _service_class: A :class:`Service` subclass. It's generally not a good idea to override it for ``@rpc`` methods. It could be necessary to override it for ``@mrpc`` methods to add events and other goodies. :param _service: Same as ``_service``. :param _wsdl_part_name: Overrides the part name attribute within wsdl input/output messages eg "parameters" """ params = list(params) def explain(f): def explain_method(**kwargs): # params and kparams are passed by the user to the @rpc family # of decorators. # kwargs is passed by spyne while sanitizing methods. it mainly # contains information about the method context like the service # class that contains the method at hand. function_name = kwargs['_default_function_name'] _service_class = kwargs.pop("_service_class", None) _self_ref_replacement = None # this block is passed straight to the descriptor _is_callback = kparams.pop('_is_callback', False) _is_async = kparams.pop('_is_async', False) _mtom = kparams.pop('_mtom', False) _in_header = kparams.pop('_in_header', None) _out_header = kparams.pop('_out_header', None) _port_type = kparams.pop('_port_type', None) _no_ctx = kparams.pop('_no_ctx', False) _aux = kparams.pop('_aux', None) _pattern = kparams.pop("_pattern", None) _patterns = kparams.pop("_patterns", []) _args = kparams.pop("_args", None) _translations = kparams.pop("_translations", None) _when = kparams.pop("_when", None) _static_when = kparams.pop("_static_when", None) _href = kparams.pop("_href", None) _logged = kparams.pop("_logged", True) _internal_key_suffix = kparams.pop('_internal_key_suffix', '') if '_service' in kparams and '_service_class' in kparams: raise LogicError("Please pass only one of '_service' and " "'_service_class'") if '_service' in kparams: _service_class = kparams.pop("_service") if '_service_class' in kparams: _service_class = kparams.pop("_service_class") _no_self = kparams.pop('_no_self', True) _event_managers = _get_event_managers(kparams) # mrpc-specific _self_ref_replacement = kwargs.pop('_self_ref_replacement', None) _default_on_null = kparams.pop('_default_on_null', False) _substitute_self_reference(params, kparams, _self_ref_replacement, _no_self) _faults = None if ('_faults' in kparams) and ('_throws' in kparams): raise ValueError("only one of '_throws ' or '_faults' arguments" "must be given -- they're synonyms.") elif '_faults' in kparams: _faults = kparams.pop('_faults') elif '_throws' in kparams: _faults = kparams.pop('_throws') _is_in_message_name_overridden = not ('_in_message_name' in kparams) _in_message_name = kparams.pop('_in_message_name', function_name) if _no_self is False and _is_in_message_name_overridden: _in_message_name = '%s.%s' % \ (_self_ref_replacement.get_type_name(), _in_message_name) _operation_name = kparams.pop('_operation_name', function_name) if _operation_name != function_name and \ _in_message_name != function_name: raise ValueError( "only one of '_operation_name' and '_in_message_name' " "arguments should be given") if _in_message_name == function_name: _in_message_name = add_request_suffix(_operation_name) if '_in_arg_names' in kparams and '_in_variable_names' in kparams: raise LogicError("Use either '_in_arg_names' or " "'_in_variable_names', not both.") elif '_in_arg_names' in kparams: _in_arg_names = kparams.pop('_in_arg_names') elif '_in_variable_names' in kparams: _in_arg_names = kparams.pop('_in_variable_names') else: _in_arg_names = {} if '_udd' in kparams and '_udp' in kparams: raise LogicError("Use either '_udd' or '_udp', not both.") elif '_udd' in kparams: _udd = kparams.pop('_udd') elif '_udp' in kparams: _udd = kparams.pop('_udp') else: _udd = {} _wsdl_part_name = kparams.get('_wsdl_part_name', None) body_style = BODY_STYLE_WRAPPED body_style_str = _validate_body_style(kparams) if body_style_str.endswith('bare'): if body_style_str == 'out_bare': body_style = BODY_STYLE_OUT_BARE else: body_style = BODY_STYLE_BARE in_message = _produce_input_message(f, params, _in_message_name, _in_arg_names, _no_ctx, _no_self, _args, body_style_str, _self_ref_replacement, _wsdl_part_name) out_message = _produce_output_message(function_name, body_style_str, _self_ref_replacement, _no_self, kparams) if _logged != True: in_message.Attributes.logged = _logged out_message.Attributes.logged = _logged doc = getattr(f, '__doc__') if _pattern is not None and _patterns != []: raise ValueError("only one of '_pattern' and '_patterns' " "arguments should be given") if _pattern is not None: _patterns = [_pattern] if body_style_str.endswith('bare'): from spyne.model import ComplexModelBase ti = in_message to = out_message if issubclass(ti, ComplexModelBase) and len(ti._type_info) == 0: if not issubclass(to, ComplexModelBase) or \ len(to._type_info) > 0: body_style = BODY_STYLE_EMPTY_OUT_BARE else: body_style = BODY_STYLE_EMPTY assert _in_header is None or isinstance(_in_header, tuple) retval = MethodDescriptor(f, in_message, out_message, doc, is_callback=_is_callback, is_async=_is_async, mtom=_mtom, in_header=_in_header, out_header=_out_header, faults=_faults, parent_class=_self_ref_replacement, port_type=_port_type, no_ctx=_no_ctx, udd=_udd, class_key=function_name, aux=_aux, patterns=_patterns, body_style=body_style, args=_args, operation_name=_operation_name, no_self=_no_self, translations=_translations, when=_when, static_when=_static_when, service_class=_service_class, href=_href, internal_key_suffix=_internal_key_suffix, default_on_null=_default_on_null, event_managers=_event_managers, logged=_logged, ) if _patterns is not None and _no_self: for p in _patterns: p.hello(retval) if len(kparams) > 0: raise ValueError("Unknown kwarg(s) %r passed.", kparams) return retval explain_method.__doc__ = f.__doc__ explain_method._is_rpc = True return explain_method return explain def srpc(*params, **kparams): """Method decorator to tag a method as a remote procedure call. See :func:`spyne.decorator.rpc` for detailed information. The initial "s" stands for "static". In Spyne terms, that means no implicit first argument is passed to the user callable, which really means the method is "stateless" rather than static. It's meant to be used for existing functions that can't be changed. """ kparams["_no_ctx"] = True return rpc(*params, **kparams) def mrpc(*params, **kparams): kparams["_no_self"] = False return rpc(*params, **kparams) spyne-spyne-2.14.0/spyne/descriptor.py000066400000000000000000000256051417664205300177760ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger('spyne') from spyne import LogicError from spyne.util import six from spyne.util import DefaultAttrDict from spyne.service import Service, ServiceBaseBase from spyne.const.xml import DEFAULT_NS class BODY_STYLE_WRAPPED: pass class BODY_STYLE_EMPTY: pass class BODY_STYLE_BARE: pass class BODY_STYLE_OUT_BARE: pass class BODY_STYLE_EMPTY_OUT_BARE: pass class MethodDescriptor(object): """This class represents the method signature of an exposed service. It is produced by the :func:`spyne.decorator.srpc` decorator. """ def __init__(self, function, in_message, out_message, doc, is_callback, is_async, mtom, in_header, out_header, faults, parent_class, port_type, no_ctx, udd, class_key, aux, patterns, body_style, args, operation_name, no_self, translations, when, static_when, service_class, href, internal_key_suffix, default_on_null, event_managers, logged): self.__real_function = function """The original callable for the user code.""" self.reset_function() self.operation_name = operation_name """The base name of an operation without the request suffix, as generated by the ``@srpc`` decorator.""" self.internal_key_suffix = internal_key_suffix """A string that is appended to the internal key string. Helpful when generating services programmatically.""" self.in_message = in_message """A :class:`spyne.model.complex.ComplexModel` subclass that defines the input signature of the user function and that was automatically generated by the ``@srpc`` decorator.""" self.name = None """The public name of the function. Equals to the type_name of the in_message.""" if body_style is BODY_STYLE_BARE: self.name = in_message.Attributes.sub_name if self.name is None: self.name = self.in_message.get_type_name() self.out_message = out_message """A :class:`spyne.model.complex.ComplexModel` subclass that defines the output signature of the user function and that was automatically generated by the ``@srpc`` decorator.""" self.doc = doc """The function docstring.""" # these are not working, so they are not documented. self.is_callback = is_callback self.is_async = is_async self.mtom = mtom #"""Flag to indicate whether to use MTOM transport with SOAP.""" self.port_type = port_type #"""The portType this function belongs to.""" self.in_header = in_header """An iterable of :class:`spyne.model.complex.ComplexModel` subclasses to denote the types of header objects that this method can accept.""" self.out_header = out_header """An iterable of :class:`spyne.model.complex.ComplexModel` subclasses to denote the types of header objects that this method can emit along with its return value.""" self.faults = faults """An iterable of :class:`spyne.model.fault.Fault` subclasses to denote the types of exceptions that this method can throw.""" self.no_ctx = no_ctx """no_ctx: Boolean flag to denote whether the user code gets an implicit :class:`spyne.MethodContext` instance as first argument.""" # FIXME: Remove UDP assingment in Spyne 3 self.udp = self.udd = DefaultAttrDict(**udd) """Short for "User Defined Data", this is an empty DefaultAttrDict that can be updated by the user to pass arbitrary metadata via the ``@rpc`` decorator.""" self.class_key = class_key """ The identifier of this method in its parent :class:`spyne.service.Service` subclass.""" self.aux = aux """Value to indicate what kind of auxiliary method this is. (None means primary) Primary methods block the request as long as they're running. Their return values are returned to the client. Auxiliary ones execute asyncronously after the primary method returns, and their return values are ignored by the rpc layer. """ self.patterns = patterns """This list stores patterns which will match this callable using various elements of the request protocol. Currently, the only object supported here is the :class:`spyne.protocol.http.HttpPattern` object. """ self.body_style = body_style """One of (BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_WRAPPED).""" self.args = args """A sequence of the names of the exposed arguments, or None.""" self.no_self = no_self """When False, this passes self as first argument (before ctx) to the decorated function. This is what separates ``@rpc`` and ``@mrpc``.""" self.service_class = service_class """The Service subclass the method belongs to. If not None for ``@mrpc`` methods, a Service subclass for anything else.""" self.parent_class = parent_class """The ComplexModel subclass the method belongs to. Only set for ``@mrpc`` methods.""" self.default_on_null = default_on_null if parent_class is None and not (default_on_null is False): raise LogicError("default_on_null is only to be used inside @mrpc") # HATEOAS Stuff self.translations = translations """None or a dict of locale-translation pairs.""" self.href = href """None or a dict of locale-translation pairs.""" self.when = when """None or a callable that takes an object instance and a :class:`MethodContext` and returns a boolean value. If this callable returns ``True``, the object can process that action. """ self.static_when = static_when """None or a callable that takes an :class:`Application` instance and returns a boolean value. If true, the object can have that action registered in the interface document. """ self.event_managers = event_managers """Event managers registered with this method.""" self.logged = logged """Denotes the logging style for this method.""" if self.service_class is not None: self.event_managers.append(self.service_class.event_manager) def translate(self, locale, default): """ :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = 'en_US' if self.translations is not None: return self.translations.get(locale, default) return default @property def key(self): """The function identifier in '{namespace}name' form.""" assert not (self.in_message.get_namespace() is DEFAULT_NS) return '{%s}%s' % ( self.in_message.get_namespace(), self.in_message.get_type_name()) @property def internal_key(self): """The internal function identifier in '{namespace}name' form.""" pc = self.parent_class if pc is not None: mn = pc.__module__ on = pc.__name__ dn = self.name # prevent duplicate class name. this happens when the class is a # direct subclass of ComplexModel if dn.split('.', 1)[0] != on: return "{%s}%s.%s" % (mn, on, dn) return "{%s}%s" % (mn, dn) sc = self.service_class if sc is not None: return '{%s}%s%s' % (sc.get_internal_key(), six.get_function_name(self.function), self.internal_key_suffix) @staticmethod def get_owner_name(cls): if issubclass(cls, Service): return cls.get_service_name() return cls.__name__ def gen_interface_key(self, cls): # this is a regular service method decorated by @rpc if issubclass(cls, ServiceBaseBase): return u'{}.{}.{}'.format(cls.__module__, self.get_owner_name(cls), self.name) # this is a member method decorated by @mrpc else: mn = cls.get_namespace() or '__none__' on = cls.get_type_name() dn = self.name # prevent duplicate class name. this happens when the class is a # direct subclass of ComplexModel if dn.split(u'.', 1)[0] != on: return u'.'.join( (mn, on, dn) ) return u'.'.join( (mn, dn) ) @staticmethod def _get_class_module_name(cls): return '.'.join([frag for frag in cls.__module__.split('.') if not frag.startswith('_')]) def is_out_bare(self): return self.body_style in (BODY_STYLE_EMPTY_OUT_BARE, BODY_STYLE_EMPTY, BODY_STYLE_BARE, BODY_STYLE_OUT_BARE) def reset_function(self, val=None): if val != None: self.__real_function = val self.function = self.__real_function @property def in_header(self): return self.__in_header @in_header.setter def in_header(self, in_header): from spyne.model._base import ModelBase try: is_model = issubclass(in_header, ModelBase) except TypeError: is_model = False if is_model: in_header = (in_header,) assert in_header is None or isinstance(in_header, tuple) self.__in_header = in_header @property def out_header(self): return self.__out_header @out_header.setter def out_header(self, out_header): from spyne.model._base import ModelBase try: is_model = issubclass(out_header, ModelBase) except TypeError: is_model = False if is_model: out_header = (out_header,) assert out_header is None or isinstance(out_header, tuple) self.__out_header = out_header spyne-spyne-2.14.0/spyne/error.py000066400000000000000000000107401417664205300167430ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.error`` module contains various common exceptions that the user code can throw. """ from spyne.model.fault import Fault class InvalidCredentialsError(Fault): """Raised when requested resource is forbidden.""" CODE = 'Client.InvalidCredentialsError' STR = "You do not have permission to access this resource." def __init__(self, fault_string=STR, params=None): super(InvalidCredentialsError, self) \ .__init__(self.CODE, fault_string, detail=params) class RequestTooLongError(Fault): """Raised when request is too long.""" CODE = 'Client.RequestTooLong' def __init__(self, faultstring="Request too long"): super(RequestTooLongError, self).__init__(self.CODE, faultstring) class RequestNotAllowed(Fault): """Raised when request is incomplete.""" CODE = 'Client.RequestNotAllowed' def __init__(self, faultstring=""): super(RequestNotAllowed, self).__init__(self.CODE, faultstring) class ArgumentError(Fault): """Raised when there is a general problem with input data.""" CODE = 'Client.ArgumentError' def __init__(self, faultstring=""): super(ArgumentError, self).__init__(self.CODE, faultstring) class InvalidInputError(Fault): """Raised when there is a general problem with input data.""" def __init__(self, faultstring="", data=""): super(InvalidInputError, self) \ .__init__('Client.InvalidInput', repr((faultstring, data))) InvalidRequestError = InvalidInputError class MissingFieldError(InvalidInputError): """Raised when a mandatory value is missing.""" CODE = 'Client.InvalidInput' def __init__(self, field_name, message="Field '%s' is missing."): try: message = message % (field_name,) except TypeError: pass super(MissingFieldError, self).__init__(self.CODE, message) class ValidationError(Fault): """Raised when the input stream does not adhere to type constraints.""" CODE = 'Client.ValidationError' def __init__(self, obj, custom_msg='The value %r could not be validated.'): try: msg = custom_msg % (obj,) except TypeError: msg = custom_msg super(ValidationError, self).__init__(self.CODE, msg) class InternalError(Fault): """Raised to communicate server-side errors.""" CODE = 'Server' def __init__(self, error): super(InternalError, self) \ .__init__(self.CODE, "InternalError: An unknown error has occured.") class ResourceNotFoundError(Fault): """Raised when requested resource is not found.""" CODE = 'Client.ResourceNotFound' def __init__(self, fault_object, fault_string="Requested resource %r not found"): super(ResourceNotFoundError, self) \ .__init__(self.CODE, fault_string % (fault_object,)) class RespawnError(ResourceNotFoundError): pass class ResourceAlreadyExistsError(Fault): """Raised when requested resource already exists on server side.""" CODE = 'Client.ResourceAlreadyExists' def __init__(self, fault_object, fault_string="Resource %r already exists"): super(ResourceAlreadyExistsError, self) \ .__init__(self.CODE, fault_string % fault_object) class Redirect(Fault): """Raised when client needs to make another request for the same resource.""" CODE = 'Client.Redirect' def __init__(self, ctx, location, orig_exc=None): super(Redirect, self).__init__(self.CODE, faultstring=location) self.ctx = ctx self.location = location self.orig_exc = orig_exc def do_redirect(self): raise NotImplementedError() spyne-spyne-2.14.0/spyne/evmgr.py000066400000000000000000000062331417664205300167340ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.util.oset import oset class EventManager(object): """Spyne supports a simple event system that can be used to have repetitive boilerplate code that has to run for every method call nicely tucked away in one or more event handlers. The popular use-cases include things like database transaction management, logging and performance measurements. Various Spyne components support firing events at various stages during the request handling process, which are documented in the relevant classes. The events are stored in an ordered set. This means that the events are ran in the order they were added and adding a handler twice does not cause it to run twice. """ def __init__(self, parent, handlers={}): """Initializer for the ``EventManager`` instance. :param parent: The owner of this event manager. As of Spyne 2.13, event managers can be owned by multiple objects, in which case this property will be none. :param handlers: A dict of event name (string)/callable pairs. The dict shallow-copied to the ``EventManager`` instance. """ self.parent = parent self.handlers = dict(handlers) def add_listener(self, event_name, handler): """Register a handler for the given event name. :param event_name: The event identifier, indicated by the documentation. Usually, this is a string. :param handler: A static python function that receives a single MethodContext argument. """ handlers = self.handlers.get(event_name, oset()) handlers.add(handler) self.handlers[event_name] = handlers def del_listener(self, event_name, handler=None): if handler is None: del self.handlers[event_name] else: self.handlers[event_name].remove(handler) def fire_event(self, event_name, ctx, *args, **kwargs): """Run all the handlers for a given event name. :param event_name: The event identifier, indicated by the documentation. Usually, this is a string. :param ctx: The method context. Event-related data is conventionally stored in ctx.event attribute. """ handlers = self.handlers.get(event_name, oset()) for handler in handlers: handler(ctx, *args, **kwargs) spyne-spyne-2.14.0/spyne/interface/000077500000000000000000000000001417664205300171765ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/interface/__init__.py000066400000000000000000000030171417664205300213100ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.interface`` package contains implementations of various interface definition document standards along with the :class:`spyne.interface.Interface` class which holds all the information needed to generate those documents. """ from __future__ import print_function from spyne.interface._base import Interface from spyne.interface._base import InterfaceDocumentBase from spyne.interface._base import InterfaceDocumentsBase from spyne.interface._base import InterfaceDocuments try: from spyne.interface.xml_schema import XmlSchema from spyne.interface.wsdl.wsdl11 import Wsdl11 HAS_WSDL = True except ImportError as e: if 'No module named lxml' in e.args or "No module named 'lxml'" in e.args: HAS_WSDL = False else: raise spyne-spyne-2.14.0/spyne/interface/_base.py000066400000000000000000000522041417664205300206240ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from collections import deque, defaultdict import spyne.interface from spyne import EventManager, MethodDescriptor from spyne.util import six from spyne.model import ModelBase, Array, Iterable, ComplexModelBase from spyne.model.complex import XmlModifier from spyne.const import xml as namespace class InterfaceDocumentsBase(object): def __init__(self, interface, # *, # kwargs start here, commented due to py2 compat wsdl11): self.interface = interface self.wsdl11 = wsdl11 @property def xml_schema(self): from spyne.interface import XmlSchema assert isinstance(self.wsdl11, XmlSchema) return self.wsdl11 class InterfaceDocuments(InterfaceDocumentsBase): def __init__(self, interface): super(InterfaceDocuments, self).__init__(interface, wsdl11=None) if spyne.interface.HAS_WSDL: from spyne.interface.wsdl import Wsdl11 self.wsdl11 = Wsdl11(interface) class Interface(object): """The ``Interface`` class holds all information needed to build an interface document. :param app: A :class:`spyne.application.Application` instance. """ def __init__(self, app=None, import_base_namespaces=False, documents_container=InterfaceDocuments): self.__ns_counter = 0 self.__app = None self.url = None self.classes = {} self.imports = {} self.service_method_map = {} self.method_id_map = {} self.nsmap = {} self.prefmap = {} self.member_methods = deque() self.method_descriptor_id_to_key = {} self.service_attrs = defaultdict(dict) self.import_base_namespaces = import_base_namespaces self.app = app # this must come after app assignment self.docs = documents_container(self) @property def documents(self): return self.docs @documents.setter def documents(self, what): self.docs = what def set_app(self, value): assert self.__app is None, "One interface instance can belong to only " \ "one application instance." self.__app = value self.reset_interface() self.populate_interface() def get_app(self): return self.__app app = property(get_app, set_app) @property def services(self): if self.__app: return self.__app.services return [] def reset_interface(self): self.classes = {} self.imports = {self.get_tns(): set()} self.service_method_map = {} self.method_id_map = {} self.nsmap = dict(namespace.NSMAP) self.prefmap = dict(namespace.PREFMAP) self.member_methods = deque() self.nsmap['tns'] = self.get_tns() self.prefmap[self.get_tns()] = 'tns' self.deps = defaultdict(set) def has_class(self, cls): """Returns true if the given class is already included in the interface object somewhere.""" ns = cls.get_namespace() tn = cls.get_type_name() key = '{%s}%s' % (ns, tn) c = self.classes.get(key) if c is None: return False if issubclass(c, ComplexModelBase) and \ issubclass(cls, ComplexModelBase): o1 = getattr(cls, '__orig__', None) or cls o2 = getattr(c, '__orig__', None) or c if o1 is o2: return True # So that "Array"s and "Iterable"s don't conflict. if set((o1, o2)) == set((Array, Iterable)): return True raise ValueError("classes %r and %r have conflicting names: '%s'" % (cls, c, key)) return True def get_class(self, key): """Returns the class definition that corresponds to the given key. Keys are in '{namespace}class_name' form. Not meant to be overridden. """ return self.classes[key] def get_class_instance(self, key): """Returns the default class instance that corresponds to the given key. Keys are in '{namespace}class_name' form, a.k.a. XML QName format. Classes should not enforce arguments to the constructor. Not meant to be overridden. """ return self.classes[key]() def get_name(self): """Returns service name that is seen in the name attribute of the definitions tag. Not meant to be overridden. """ if self.app: return self.app.name def get_tns(self): """Returns default namespace that is seen in the targetNamespace attribute of the definitions tag. Not meant to be overridden. """ if self.app: return self.app.tns def add_method(self, method): """Generator method that adds the given method descriptor to the interface. Also extracts and yields all the types found in there. :param method: A :class:`MethodDescriptor` instance :returns: Sequence of :class:`spyne.model.ModelBase` subclasses. """ if not (method.in_header is None): if not isinstance(method.in_header, (list, tuple)): method.in_header = (method.in_header,) for in_header in method.in_header: in_header.resolve_namespace(in_header, self.get_tns()) if method.aux is None: yield in_header in_header_ns = in_header.get_namespace() if in_header_ns != self.get_tns() and \ self.is_valid_import(in_header_ns): self.imports[self.get_tns()].add(in_header_ns) if not (method.out_header is None): if not isinstance(method.out_header, (list, tuple)): method.out_header = (method.out_header,) for out_header in method.out_header: out_header.resolve_namespace(out_header, self.get_tns()) if method.aux is None: yield out_header out_header_ns = out_header.get_namespace() if out_header_ns != self.get_tns() and \ self.is_valid_import(out_header_ns): self.imports[self.get_tns()].add(out_header_ns) if method.faults is None: method.faults = [] elif not (isinstance(method.faults, (list, tuple))): method.faults = (method.faults,) for fault in method.faults: fault.__namespace__ = self.get_tns() fault.resolve_namespace(fault, self.get_tns()) if method.aux is None: yield fault method.in_message.resolve_namespace(method.in_message, self.get_tns()) in_message_ns = method.in_message.get_namespace() if in_message_ns != self.get_tns() and \ self.is_valid_import(in_message_ns): self.imports[self.get_tns()].add(method.in_message.get_namespace()) if method.aux is None: yield method.in_message method.out_message.resolve_namespace(method.out_message, self.get_tns()) assert not method.out_message.get_type_name() is method.out_message.Empty out_message_ns = method.out_message.get_namespace() if out_message_ns != self.get_tns() and \ self.is_valid_import(out_message_ns): self.imports[self.get_tns()].add(out_message_ns) if method.aux is None: yield method.out_message for p in method.patterns: p.endpoint = method def process_method(self, s, method): assert isinstance(method, MethodDescriptor) method_key = u'{%s}%s' % (self.app.tns, method.name) if issubclass(s, ComplexModelBase): method_object_name = method.name.split('.', 1)[0] if s.get_type_name() != method_object_name: method_key = u'{%s}%s.%s' % (self.app.tns, s.get_type_name(), method.name) key = method.gen_interface_key(s) if key in self.method_id_map: c = self.method_id_map[key].parent_class if c is None: pass elif c is s: pass elif c.__orig__ is None: assert c is s.__orig__, "%r.%s conflicts with %r.%s" % \ (c, key, s.__orig__, key) elif s.__orig__ is None: assert c.__orig__ is s, "%r.%s conflicts with %r.%s" % \ (c.__orig__, key, s, key) else: assert c.__orig__ is s.__orig__, "%r.%s conflicts with %r.%s" % \ (c.__orig__, key, s.__orig__, key) return logger.debug(' adding method %s.%s to match %r tag.', method.get_owner_name(s), six.get_function_name(method.function), method_key) self.method_id_map[key] = method val = self.service_method_map.get(method_key, None) if val is None: val = self.service_method_map[method_key] = [] if len(val) == 0: val.append(method) elif method.aux is not None: val.append(method) elif val[0].aux is not None: val.insert(method, 0) else: om = val[0] os = om.service_class if os is None: os = om.parent_class raise ValueError("\nThe message %r defined in both '%s.%s'" " and '%s.%s'" % (method.name, s.__module__, s.__name__, os.__module__, os.__name__)) def check_method(self, method): """Override this if you need to cherry-pick methods added to the interface document.""" return True def populate_interface(self, types=None): """Harvests the information stored in individual classes' _type_info dictionaries. It starts from function definitions and includes only the used objects. """ # populate types for s in self.services: logger.debug("populating %s types...", s.get_internal_key()) for method in s.public_methods.values(): if method.in_header is None: method.in_header = s.__in_header__ if method.out_header is None: method.out_header = s.__out_header__ if method.aux is None: method.aux = s.__aux__ if method.aux is not None: method.aux.methods.append(method.gen_interface_key(s)) if not self.check_method(method): logger.debug("method %s' discarded by check_method", method.class_key) continue logger.debug(" enumerating classes for method '%s'", method.class_key) for cls in self.add_method(method): self.add_class(cls) # populate additional types for c in self.app.classes: self.add_class(c) # populate call routes for service methods for s in self.services: self.service_attrs[s]['tns'] = self.get_tns() logger.debug("populating '%s.%s' routes...", s.__module__, s.__name__) for method in s.public_methods.values(): self.process_method(s, method) # populate call routes for member methods for cls, method in self.member_methods: should_we = True if method.static_when is not None: should_we = method.static_when(self.app) logger.debug("static_when returned %r for %s " "while populating methods", should_we, method.internal_key) if should_we: s = method.service_class if s is not None: if method.in_header is None: method.in_header = s.__in_header__ if method.out_header is None: method.out_header = s.__out_header__ # FIXME: There's no need to process aux info here as it's # not currently known how to write aux member methods in the # first place. self.process_method(cls.__orig__ or cls, method) # populate method descriptor id to method key map self.method_descriptor_id_to_key = dict(((id(v[0]), k) for k,v in self.service_method_map.items())) logger.debug("From this point on, you're not supposed to make any " "changes to the class and method structure of the exposed " "services.") tns = property(get_tns) def get_namespace_prefix(self, ns): """Returns the namespace prefix for the given namespace. Creates a new one automatically if it doesn't exist. Not meant to be overridden. """ if not (isinstance(ns, str) or isinstance(ns, six.text_type)): raise TypeError(ns) if not (ns in self.prefmap): pref = "s%d" % self.__ns_counter while pref in self.nsmap: self.__ns_counter += 1 pref = "s%d" % self.__ns_counter self.prefmap[ns] = pref self.nsmap[pref] = ns self.__ns_counter += 1 else: pref = self.prefmap[ns] return pref def add_class(self, cls, add_parent=True): if self.has_class(cls): return ns = cls.get_namespace() tn = cls.get_type_name() assert ns is not None, ('either assign a namespace to the class or call' ' cls.resolve_namespace(cls, "some_default_ns") on it.') if not (ns in self.imports) and self.is_valid_import(ns): self.imports[ns] = set() class_key = '{%s}%s' % (ns, tn) logger.debug(' adding class %r for %r', repr(cls), class_key) assert class_key not in self.classes, ("Somehow, you're trying to " "overwrite %r by %r for class key %r." % (self.classes[class_key], cls, class_key)) assert not (cls.get_type_name() is cls.Empty), cls self.deps[cls] # despite the appearances, this is not totally useless. self.classes[class_key] = cls if ns == self.get_tns(): self.classes[tn] = cls # add parent class extends = getattr(cls, '__extends__', None) while extends is not None and \ (extends.get_type_name() is ModelBase.Empty): extends = getattr(extends, '__extends__', None) if add_parent and extends is not None: assert issubclass(extends, ModelBase) self.deps[cls].add(extends) self.add_class(extends) parent_ns = extends.get_namespace() if parent_ns != ns and not parent_ns in self.imports[ns] and \ self.is_valid_import(parent_ns): self.imports[ns].add(parent_ns) logger.debug(" importing %r to %r because %r extends %r", parent_ns, ns, cls.get_type_name(), extends.get_type_name()) # add fields if issubclass(cls, ComplexModelBase): for k, v in cls._type_info.items(): if v is None: continue self.deps[cls].add(v) logger.debug(" adding %s.%s = %r", cls.get_type_name(), k, v) if v.get_namespace() is None: v.resolve_namespace(v, ns) self.add_class(v) if v.get_namespace() is None and cls.get_namespace() is not None: v.resolve_namespace(v, cls.get_namespace()) child_ns = v.get_namespace() if child_ns != ns and not child_ns in self.imports[ns] and \ self.is_valid_import(child_ns): self.imports[ns].add(child_ns) logger.debug(" importing %r to %r for %s.%s(%r)", child_ns, ns, cls.get_type_name(), k, v) if issubclass(v, XmlModifier): self.add_class(v.type) child_ns = v.type.get_namespace() if child_ns != ns and not child_ns in self.imports[ns] and \ self.is_valid_import(child_ns): self.imports[ns].add(child_ns) logger.debug(" importing %r to %r for %s.%s(%r)", child_ns, ns, v.get_type_name(), k, v.type) if cls.Attributes.methods is not None: logger.debug(" populating member methods for '%s.%s'...", cls.get_namespace(), cls.get_type_name()) for method_key, descriptor in cls.Attributes.methods.items(): assert hasattr(cls, method_key) should_we = True if descriptor.static_when is not None: should_we = descriptor.static_when(self.app) logger.debug("static_when returned %r for %s " "while populating classes", should_we, descriptor.internal_key) if should_we: self.member_methods.append((cls, descriptor)) for c in self.add_method(descriptor): self.add_class(c) if cls.Attributes._subclasses is not None: logger.debug(" adding subclasses of '%s.%s'...", cls.get_namespace(), cls.get_type_name()) for c in cls.Attributes._subclasses: c.resolve_namespace(c, ns) child_ns = c.get_namespace() if child_ns == ns: if not self.has_class(c): self.add_class(c, add_parent=False) self.deps[c].add(cls) else: logger.debug(" not adding %r to %r because it would " "cause circular imports because %r extends %r and " "they don't have the same namespace", child_ns, ns, c.get_type_name(), cls.get_type_name()) def is_valid_import(self, ns): """This will return False for base namespaces unless told otherwise.""" if ns is None: raise ValueError(ns) return self.import_base_namespaces or not (ns in namespace.PREFMAP) class InterfaceDocumentBase(object): """Base class for all interface document implementations. :param interface: A :class:`spyne.interface.InterfaceBase` instance. """ def __init__(self, interface): self.interface = interface self.event_manager = EventManager(self) def build_interface_document(self): """This function is supposed to be called just once, as late as possible into the process start. It builds the interface document and caches it somewhere. The overriding function should never call the overridden function as this may result in the same event firing more than once. """ raise NotImplementedError('Extend and override.') def get_interface_document(self): """This function is called by server transports that try to satisfy the request for the interface document. This should just return a previously cached interface document. """ raise NotImplementedError('Extend and override.') spyne-spyne-2.14.0/spyne/interface/wsdl/000077500000000000000000000000001417664205300201475ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/interface/wsdl/__init__.py000066400000000000000000000017531417664205300222660ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.interface.wsdl`` package contains an implementation of the subset Wsdl 1.1 standard, and awaits for volunteers for implementing the brand new Wsdl 2.0 standard. """ from spyne.interface.wsdl.wsdl11 import Wsdl11 spyne-spyne-2.14.0/spyne/interface/wsdl/defn.py000066400000000000000000000054571417664205300214500ustar00rootroot00000000000000 from spyne.util.six import add_metaclass from spyne.const import xml from spyne.model.primitive import Unicode from spyne.model.complex import XmlAttribute from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModelMeta from spyne.interface.xml_schema.defn import XmlSchema10 @add_metaclass(ComplexModelMeta) class Wsdl11Base(ComplexModelBase): __namespace__ = xml.NS_WSDL11 @add_metaclass(ComplexModelMeta) class Soap11Base(ComplexModelBase): __namespace__ = xml.NS_WSDL11_SOAP class Types(Wsdl11Base): schema = XmlSchema10.customize(max_occurs="unbounded") class MessagePart(Wsdl11Base): element = XmlAttribute(Unicode) name = XmlAttribute(Unicode) class Message(Wsdl11Base): part = MessagePart name = XmlAttribute(Unicode) class SoapBodyDefinition(Wsdl11Base): use = XmlAttribute(Unicode) class SoapHeaderDefinition(Wsdl11Base): use = XmlAttribute(Unicode) message = XmlAttribute(Unicode) part = XmlAttribute(Unicode) class OperationMode(Wsdl11Base): name = XmlAttribute(Unicode) message = XmlAttribute(Unicode) soap_body = SoapBodyDefinition.customize(sub_ns=xml.NS_WSDL11_SOAP, sub_name="body") soap_header = SoapHeaderDefinition.customize(sub_ns=xml.NS_WSDL11_SOAP, sub_name="header") class SoapOperation(Wsdl11Base): soapAction = XmlAttribute(Unicode) style = XmlAttribute(Unicode) class Operation(Wsdl11Base): input = OperationMode output = OperationMode soap_operation = SoapOperation.customize(sub_ns=xml.NS_WSDL11_SOAP, sub_name="operation") parameterOrder = XmlAttribute(Unicode) class PortType(Wsdl11Base): name = XmlAttribute(Unicode) operation = Operation.customize(max_occurs="unbounded") class SoapBinding(Soap11Base): style = XmlAttribute(Unicode) transport = XmlAttribute(Unicode) class Binding(Wsdl11Base): name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) location = XmlAttribute(Unicode) soap_binding = SoapBinding.customize(sub_ns=xml.NS_WSDL11_SOAP, sub_name="binding") class PortAddress(Soap11Base): location = XmlAttribute(Unicode) class ServicePort(Wsdl11Base): name = XmlAttribute(Unicode) binding = XmlAttribute(Unicode) address = PortAddress.customize(sub_ns=xml.NS_WSDL11_SOAP) class Service(Wsdl11Base): port = ServicePort name = XmlAttribute(Unicode) class Wsdl11(Wsdl11Base): _type_info = [ ('types', Types), ('message', Message.customize(max_occurs="unbounded")), ('service', Service), ('portType', PortType), ('binding', Binding), ] spyne-spyne-2.14.0/spyne/interface/wsdl/wsdl11.py000066400000000000000000000552371417664205300216500ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.interface.wsdl.wsdl11`` module contains an implementation of a subset of the Wsdl 1.1 document standard and its helper methods. """ import logging logger = logging.getLogger(__name__) import re import spyne.const.xml as ns from spyne.util import six from lxml import etree from lxml.builder import E from lxml.etree import SubElement from spyne.const.xml import WSDL11, XSD, NS_WSA, PLINK from spyne.interface.xml_schema import XmlSchema REGEX_WSDL = re.compile('[.?]wsdl$') PREF_WSA = ns.PREFMAP[NS_WSA] _in_header_msg_suffix = 'InHeaderMsg' _out_header_msg_suffix = 'OutHeaderMsg' def check_method_port(service, method): if len(service.__port_types__) != 0 and method.port_type is None: raise ValueError(""" A port must be declared in the RPC decorator if the service class declares a list of ports Method: %r """ % method.name) if (not method.port_type is None) and len(service.__port_types__) == 0: raise ValueError(""" The rpc decorator has declared a port while the service class has not. Remove the port declaration from the rpc decorator or add a list of ports to the service class """) try: if (not method.port_type is None): index = service.__port_types__.index(method.port_type) except ValueError as e: raise ValueError(""" The port specified in the rpc decorator does not match any of the ports defined by the service class """) class Wsdl11(XmlSchema): """The implementation of the Wsdl 1.1 interface definition document standard which is avaible here: http://www.w3.org/TR/wsdl :param app: The parent application. :param _with_partnerlink: Include the partnerLink tag in the wsdl. Supported events: * document_built: Called right after the document is built. The handler gets the ``Wsdl11`` instance as the only argument. Also called by XmlSchema class. * wsdl_document_built: Called right after the document is built. The handler gets the ``Wsdl11`` instance as the only argument. Only called from this class. """ #:param import_base_namespaces: Include imports for base namespaces like # xsd, xsi, wsdl, etc. def __init__(self, interface=None, xsl_href=None, _with_partnerlink=False, element_form_default='qualified'): super(Wsdl11, self).__init__( interface, element_form_default=element_form_default) self._with_plink = _with_partnerlink self.xsl_href = xsl_href self.port_type_dict = {} self.service_elt_dict = {} self.root_elt = None self.service_elt = None self.__wsdl = None self.validation_schema = None def _get_binding_name(self, port_type_name): return port_type_name # subclasses override to control port names. def _get_or_create_port_type(self, pt_name): """Creates a wsdl:portType element.""" pt = None if not pt_name in self.port_type_dict: pt = SubElement(self.root_elt, WSDL11("portType")) pt.set('name', pt_name) self.port_type_dict[pt_name] = pt else: pt = self.port_type_dict[pt_name] return pt def _get_or_create_service_node(self, service_name): """Builds a wsdl:service element.""" ser = None if not service_name in self.service_elt_dict: ser = SubElement(self.root_elt, WSDL11("service")) ser.set('name', service_name) self.service_elt_dict[service_name] = ser else: ser = self.service_elt_dict[service_name] return ser def get_interface_document(self): return self.__wsdl def build_interface_document(self, url): """Build the wsdl for the application.""" self.build_schema_nodes() self.url = REGEX_WSDL.sub('', url) service_name = self.interface.get_name() # create wsdl root node self.root_elt = root = etree.Element(WSDL11("definitions"), nsmap=self.interface.nsmap) if self.xsl_href is not None: # example: # " # pi.attrib.__setitem__ is ignored, so we get a proper list of # attributes to pass with the following hack. pitext = etree.tostring(etree.Element("dummy", dict(type='text/xsl', href=self.xsl_href)), encoding='unicode') \ .split(" ", 1)[-1][:-2] pi = etree.ProcessingInstruction("xml-stylesheet", pitext) self.root_elt.addprevious(pi) self.root_tree = root.getroottree() root.set('targetNamespace', self.interface.tns) root.set('name', service_name) # create types node types = SubElement(root, WSDL11("types")) for s in self.schema_dict.values(): types.append(s) messages = set() for s in self.interface.services: self.add_messages_for_methods(s, root, messages) if self._with_plink: plink = SubElement(root, PLINK("partnerLinkType")) plink.set('name', service_name) self.__add_partner_link(service_name, plink) # create service nodes in advance. they're to be filled in subsequent # add_port_type calls. for s in self.interface.services: if not s.is_auxiliary(): self._get_or_create_service_node(self._get_applied_service_name(s)) # create portType nodes for s in self.interface.services: if not s.is_auxiliary(): self.add_port_type(s, root, service_name, types, self.url) cb_binding = None for s in self.interface.services: if not s.is_auxiliary(): cb_binding = self.add_bindings_for_methods(s, root, service_name, cb_binding) if self.interface.app.transport is None: raise Exception("You must set the 'transport' property of the " "parent 'Application' instance") self.event_manager.fire_event('document_built', self) self.event_manager.fire_event('wsdl_document_built', self) self.__wsdl = etree.tostring(self.root_tree, xml_declaration=True, encoding="UTF-8") def __add_partner_link(self, service_name, plink): """Add the partnerLinkType node to the wsdl.""" ns_tns = self.interface.tns pref_tns = self.interface.get_namespace_prefix(ns_tns) role = SubElement(plink, PLINK("role")) role.set('name', service_name) plink_port_type = SubElement(role, PLINK("portType")) plink_port_type.set('name', '%s:%s' % (pref_tns, service_name)) if self._has_callbacks(): role = SubElement(plink, PLINK("role")) role.set('name', '%sCallback' % service_name) plink_port_type = SubElement(role, PLINK("portType")) plink_port_type.set('name', '%s:%sCallback' % (pref_tns, service_name)) def _add_port_to_service(self, service, port_name, binding_name): """ Builds a wsdl:port for a service and binding""" pref_tns = self.interface.get_namespace_prefix(self.interface.tns) wsdl_port = SubElement(service, WSDL11("port")) wsdl_port.set('name', port_name) wsdl_port.set('binding', '%s:%s' % (pref_tns, binding_name)) addr = SubElement(wsdl_port, ns.get_binding_ns(self.interface.app.in_protocol.type)("address")) addr.set('location', self.url) def _has_callbacks(self): for s in self.interface.services: if s._has_callbacks(): return True return False def _get_applied_service_name(self, service): if service.get_service_name() is None: # This is the default behavior. i.e. no service interface is # defined in the service heading if len(self.interface.services) == 1: retval = self.get_name() else: retval = service.get_service_class_name() else: retval = service.get_service_name() return retval def add_port_type(self, service, root, service_name, types, url): # FIXME: I don't think this call is working. cb_port_type = self._add_callbacks(service, root, types, service_name, url) applied_service_name = self._get_applied_service_name(service) port_binding_names = [] port_type_list = service.get_port_types() if len(port_type_list) > 0: for port_type_name in port_type_list: port_type = self._get_or_create_port_type(port_type_name) port_type.set('name', port_type_name) binding_name = self._get_binding_name(port_type_name) port_binding_names.append((port_type_name, binding_name)) else: port_type = self._get_or_create_port_type(service_name) port_type.set('name', service_name) binding_name = self._get_binding_name(service_name) port_binding_names.append((service_name, binding_name)) for method in service.public_methods.values(): check_method_port(service, method) if method.is_callback: operation = SubElement(cb_port_type, WSDL11("operation")) else: operation = SubElement(port_type, WSDL11("operation")) operation.set('name', method.operation_name) if method.doc is not None: operation.append(E(WSDL11("documentation"), method.doc)) operation.set('parameterOrder', method.in_message.get_element_name()) op_input = SubElement(operation, WSDL11("input")) op_input.set('name', method.in_message.get_element_name()) op_input.set('message', method.in_message.get_element_name_ns(self.interface)) if (not method.is_callback) and (not method.is_async): op_output = SubElement(operation, WSDL11("output")) op_output.set('name', method.out_message.get_element_name()) op_output.set('message', method.out_message.get_element_name_ns( self.interface)) if not (method.faults is None): for f in method.faults: fault = SubElement(operation, WSDL11("fault")) fault.set('name', f.get_type_name()) fault.set('message', '%s:%s' % ( f.get_namespace_prefix(self.interface), f.get_type_name())) ser = self.service_elt_dict[applied_service_name] for port_name, binding_name in port_binding_names: self._add_port_to_service(ser, port_name, binding_name) def _add_message_for_object(self, root, messages, obj, message_name): if obj is not None and not (message_name in messages): messages.add(message_name) message = SubElement(root, WSDL11("message")) message.set('name', message_name) if isinstance(obj, (list, tuple)): objs = obj else: objs = (obj,) for obj in objs: part = SubElement(message, WSDL11("part")) part.set('name', obj.get_wsdl_part_name()) part.set('element', obj.get_element_name_ns(self.interface)) def add_messages_for_methods(self, service, root, messages): for method in service.public_methods.values(): self._add_message_for_object(root, messages, method.in_message, method.in_message.get_element_name()) self._add_message_for_object(root, messages, method.out_message, method.out_message.get_element_name()) if method.in_header is not None: if len(method.in_header) > 1: in_header_message_name = ''.join((method.name, _in_header_msg_suffix)) else: in_header_message_name = method.in_header[0].get_type_name() self._add_message_for_object(root, messages, method.in_header, in_header_message_name) if method.out_header is not None: if len(method.out_header) > 1: out_header_message_name = ''.join((method.name, _out_header_msg_suffix)) else: out_header_message_name = method.out_header[0].get_type_name() self._add_message_for_object(root, messages, method.out_header, out_header_message_name) for fault in method.faults: self._add_message_for_object(root, messages, fault, fault.get_type_name()) def add_bindings_for_methods(self, service, root, service_name, cb_binding): pref_tns = self.interface.get_namespace_prefix(self.interface.get_tns()) input_binding_ns = ns.get_binding_ns(self.interface.app.in_protocol.type) output_binding_ns = ns.get_binding_ns(self.interface.app.out_protocol.type) def inner(method, binding): operation = etree.Element(WSDL11("operation")) operation.set('name', method.operation_name) soap_operation = SubElement(operation, input_binding_ns("operation")) soap_operation.set('soapAction', method.operation_name) soap_operation.set('style', 'document') # get input input = SubElement(operation, WSDL11("input")) input.set('name', method.in_message.get_element_name()) soap_body = SubElement(input, input_binding_ns("body")) soap_body.set('use', 'literal') # get input soap header in_header = method.in_header if in_header is None: in_header = service.__in_header__ if not (in_header is None): if isinstance(in_header, (list, tuple)): in_headers = in_header else: in_headers = (in_header,) if len(in_headers) > 1: in_header_message_name = ''.join((method.name, _in_header_msg_suffix)) else: in_header_message_name = in_headers[0].get_type_name() for header in in_headers: soap_header = SubElement(input, input_binding_ns('header')) soap_header.set('use', 'literal') soap_header.set('message', '%s:%s' % ( header.get_namespace_prefix(self.interface), in_header_message_name)) soap_header.set('part', header.get_type_name()) if not (method.is_async or method.is_callback): output = SubElement(operation, WSDL11("output")) output.set('name', method.out_message.get_element_name()) soap_body = SubElement(output, output_binding_ns("body")) soap_body.set('use', 'literal') # get output soap header out_header = method.out_header if out_header is None: out_header = service.__out_header__ if not (out_header is None): if isinstance(out_header, (list, tuple)): out_headers = out_header else: out_headers = (out_header,) if len(out_headers) > 1: out_header_message_name = ''.join((method.name, _out_header_msg_suffix)) else: out_header_message_name = out_headers[0].get_type_name() for header in out_headers: soap_header = SubElement(output, output_binding_ns("header")) soap_header.set('use', 'literal') soap_header.set('message', '%s:%s' % ( header.get_namespace_prefix(self.interface), out_header_message_name)) soap_header.set('part', header.get_type_name()) if not (method.faults is None): for f in method.faults: wsdl_fault = SubElement(operation, WSDL11("fault")) wsdl_fault.set('name', f.get_type_name()) soap_fault = SubElement(wsdl_fault, input_binding_ns("fault")) soap_fault.set('name', f.get_type_name()) soap_fault.set('use', 'literal') if method.is_callback: relates_to = SubElement(input, input_binding_ns("header")) relates_to.set('message', '%s:RelatesToHeader' % pref_tns) relates_to.set('part', 'RelatesTo') relates_to.set('use', 'literal') cb_binding.append(operation) else: if method.is_async: rt_header = SubElement(input, input_binding_ns("header")) rt_header.set('message', '%s:ReplyToHeader' % pref_tns) rt_header.set('part', 'ReplyTo') rt_header.set('use', 'literal') mid_header = SubElement(input, input_binding_ns("header")) mid_header.set('message', '%s:MessageIDHeader' % pref_tns) mid_header.set('part', 'MessageID') mid_header.set('use', 'literal') binding.append(operation) port_type_list = service.get_port_types() if len(port_type_list) > 0: for port_type_name in port_type_list: # create binding nodes binding = SubElement(root, WSDL11("binding")) binding.set('name', self._get_binding_name(port_type_name)) binding.set('type', '%s:%s'% (pref_tns, port_type_name)) transport = SubElement(binding, input_binding_ns("binding")) transport.set('style', 'document') transport.set('transport', self.interface.app.transport) for m in service.public_methods.values(): if m.port_type == port_type_name: inner(m, binding) else: # here is the default port. if cb_binding is None: cb_binding = SubElement(root, WSDL11("binding")) cb_binding.set('name', service_name) cb_binding.set('type', '%s:%s'% (pref_tns, service_name)) transport = SubElement(cb_binding, input_binding_ns("binding")) transport.set('style', 'document') transport.set('transport', self.interface.app.transport) for m in service.public_methods.values(): inner(m, cb_binding) return cb_binding # FIXME: I don't think this is working. def _add_callbacks(self, service, root, types, service_name, url): ns_tns = self.interface.get_tns() pref_tns = 'tns' input_binding_ns = ns.get_binding_ns(self.interface.app.in_protocol.type) cb_port_type = None # add necessary async headers # WS-Addressing -> RelatesTo ReplyTo MessageID # callback porttype if service._has_callbacks(): wsa_schema = SubElement(types, XSD("schema")) wsa_schema.set("targetNamespace", '%sCallback' % ns_tns) wsa_schema.set("elementFormDefault", "qualified") import_ = SubElement(wsa_schema, XSD("import")) import_.set("namespace", NS_WSA) import_.set("schemaLocation", NS_WSA) relt_message = SubElement(root, WSDL11("message")) relt_message.set('name', 'RelatesToHeader') relt_part = SubElement(relt_message, WSDL11("part")) relt_part.set('name', 'RelatesTo') relt_part.set('element', '%s:RelatesTo' % PREF_WSA) reply_message = SubElement(root, WSDL11("message")) reply_message.set('name', 'ReplyToHeader') reply_part = SubElement(reply_message, WSDL11("part")) reply_part.set('name', 'ReplyTo') reply_part.set('element', '%s:ReplyTo' % PREF_WSA) id_header = SubElement(root, WSDL11("message")) id_header.set('name', 'MessageIDHeader') id_part = SubElement(id_header, WSDL11("part")) id_part.set('name', 'MessageID') id_part.set('element', '%s:MessageID' % PREF_WSA) # make portTypes cb_port_type = SubElement(root, WSDL11("portType")) cb_port_type.set('name', '%sCallback' % service_name) cb_service_name = '%sCallback' % service_name cb_service = SubElement(root, WSDL11("service")) cb_service.set('name', cb_service_name) cb_wsdl_port = SubElement(cb_service, WSDL11("port")) cb_wsdl_port.set('name', cb_service_name) cb_wsdl_port.set('binding', '%s:%s' % (pref_tns, cb_service_name)) cb_address = SubElement(cb_wsdl_port, input_binding_ns("address")) cb_address.set('location', url) return cb_port_type spyne-spyne-2.14.0/spyne/interface/xml_schema/000077500000000000000000000000001417664205300213165ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/interface/xml_schema/__init__.py000066400000000000000000000020551417664205300234310ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The spyne.interface.xml_schema package contains an implementation of a subset of the Xml Schema 1.0 standard. Volunteers are needed to see whether the brand new Xml Schema 1.1 standard is worth the trouble, and patch as necessary. """ from spyne.interface.xml_schema._base import XmlSchema spyne-spyne-2.14.0/spyne/interface/xml_schema/_base.py000066400000000000000000000250131417664205300227420ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger('.'.join(__name__.split(".")[:-1])) import os import shutil import tempfile import spyne.const.xml as ns from lxml import etree from itertools import chain from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.util.toposort import toposort2 from spyne.model import SimpleModel, ByteArray, ComplexModelBase, Fault, \ Decimal, DateTime, Date, Time, Unicode from spyne.model.enum import EnumBase from spyne.interface import InterfaceDocumentBase from spyne.interface.xml_schema.model import byte_array_add from spyne.interface.xml_schema.model import simple_add from spyne.interface.xml_schema.model import complex_add from spyne.interface.xml_schema.model import fault_add from spyne.interface.xml_schema.model import enum_add from spyne.interface.xml_schema.model import simple_get_restriction_tag from spyne.interface.xml_schema.model import unicode_get_restriction_tag from spyne.interface.xml_schema.model import Tget_range_restriction_tag _add_handlers = cdict({ object: lambda interface, cls, tags: None, ByteArray: byte_array_add, SimpleModel: simple_add, ComplexModelBase: complex_add, Fault: fault_add, EnumBase: enum_add, }) _get_restriction_tag_handlers = cdict({ object: lambda self, cls: None, SimpleModel: simple_get_restriction_tag, Unicode: unicode_get_restriction_tag, Decimal: Tget_range_restriction_tag(Decimal), DateTime: Tget_range_restriction_tag(DateTime), Time: Tget_range_restriction_tag(Time), Date: Tget_range_restriction_tag(Date), }) _ns_xsd = ns.NS_XSD _ns_wsa = ns.NS_WSA _ns_wsdl = ns.NS_WSDL11 _ns_soap = ns.NS_WSDL11_SOAP _pref_wsa = ns.PREFMAP[_ns_wsa] class SchemaInfo(object): def __init__(self): self.elements = odict() self.types = odict() class XmlSchema(InterfaceDocumentBase): """The implementation of a subset of the Xml Schema 1.0 object definition document standard. The standard is available in three parts as follows: http://www.w3.org/TR/xmlschema-0/ http://www.w3.org/TR/xmlschema-1/ http://www.w3.org/TR/xmlschema-2/ :param interface: A :class:`spyne.interface.InterfaceBase` instance. Supported events: * document_built: Called right after the document is built. The handler gets the ``XmlSchema`` instance as the only argument. * xml_document_built: Called right after the document is built. The handler gets the ``XmlSchema`` instance as the only argument. Only called from this class. """ def __init__(self, interface, element_form_default='qualified'): super(XmlSchema, self).__init__(interface) self.element_form_default = element_form_default assert element_form_default in ('qualified', 'unqualified') self.schema_dict = {} self.validation_schema = None pref = self.interface.prefmap[self.interface.app.tns] self.namespaces = odict({pref: SchemaInfo()}) self.complex_types = set() def add(self, cls, tags): if not (cls in tags): tags.add(cls) handler = _add_handlers[cls] handler(self, cls, tags) def get_restriction_tag(self, cls): handler = _get_restriction_tag_handlers[cls] return handler(self, cls) def build_schema_nodes(self, with_schema_location=False): self.schema_dict = {} tags = set() for cls in chain.from_iterable(toposort2(self.interface.deps)): self.add(cls, tags) for pref in self.namespaces: schema = self.get_schema_node(pref) # append import tags for namespace in self.interface.imports[self.interface.nsmap[pref]]: import_ = etree.SubElement(schema, ns.XSD('import')) import_.set("namespace", namespace) import_pref = self.interface.get_namespace_prefix(namespace) if with_schema_location and \ self.namespaces.get(import_pref, False): import_.set('schemaLocation', "%s.xsd" % import_pref) sl = ns.schema_location.get(namespace, None) if not (sl is None): import_.set('schemaLocation', sl) # append simpleType and complexType tags for node in self.namespaces[pref].types.values(): schema.append(node) # append element tags for node in self.namespaces[pref].elements.values(): schema.append(node) self.add_missing_elements_for_methods() self.event_manager.fire_event('document_built', self) self.event_manager.fire_event('xml_document_built', self) def add_missing_elements_for_methods(self): def missing_methods(): for service in self.interface.services: for method in service.public_methods.values(): if method.aux is None: yield method pref_tns = self.interface.prefmap[self.interface.tns] elements = self.get_schema_info(pref_tns).elements schema_root = self.schema_dict[pref_tns] for method in missing_methods(): name = method.in_message.Attributes.sub_name if name is None: name = method.in_message.get_type_name() if not name in elements: element = etree.Element(ns.XSD('element')) element.set('name', name) element.set('type', method.in_message.get_type_name_ns( self.interface)) elements[name] = element schema_root.append(element) if method.out_message is not None: name = method.out_message.Attributes.sub_name if name is None: name = method.out_message.get_type_name() if not name in elements: element = etree.Element(ns.XSD('element')) element.set('name', name) element.set('type', method.out_message \ .get_type_name_ns(self.interface)) elements[name] = element schema_root.append(element) def build_validation_schema(self): """Build application schema specifically for xml validation purposes.""" self.build_schema_nodes(with_schema_location=True) pref_tns = self.interface.get_namespace_prefix(self.interface.tns) tmp_dir_name = tempfile.mkdtemp(prefix='spyne') logger.debug("generating schema for targetNamespace=%r, prefix: " "%r in dir %r" % (self.interface.tns, pref_tns, tmp_dir_name)) try: # serialize nodes to files for k, v in self.schema_dict.items(): file_name = os.path.join(tmp_dir_name, "%s.xsd" % k) with open(file_name, 'wb') as f: etree.ElementTree(v).write(f, pretty_print=True) logger.debug("writing %r for ns %s" % (file_name, self.interface.nsmap[k])) with open(os.path.join(tmp_dir_name, "%s.xsd" % pref_tns), 'r') as f: try: self.validation_schema = etree.XMLSchema(etree.parse(f)) except Exception: f.seek(0) logger.error("This could be a Spyne error. Unless you're " "sure the reason for this error is outside " "Spyne, please open a new issue with a " "minimal test case that reproduces it.") raise shutil.rmtree(tmp_dir_name) logger.debug("Schema built. Removed %r" % tmp_dir_name) except Exception as e: logger.exception(e) logger.error("The schema files are left at: %r" % tmp_dir_name) raise def get_schema_node(self, pref): """Return schema node for the given namespace prefix.""" if not (pref in self.schema_dict): schema = etree.Element(ns.XSD('schema'), nsmap=self.interface.nsmap) schema.set("targetNamespace", self.interface.nsmap[pref]) schema.set("elementFormDefault", self.element_form_default) self.schema_dict[pref] = schema else: schema = self.schema_dict[pref] return schema def get_interface_document(self): return self.schema_dict def build_interface_document(self): self.build_schema_nodes() def add_element(self, cls, node): pref = cls.get_element_name_ns(self.interface).split(":")[0] schema_info = self.get_schema_info(pref) name = cls.Attributes.sub_name or cls.get_type_name() schema_info.elements[name] = node def add_simple_type(self, cls, node): tn = cls.get_type_name() pref = cls.get_namespace_prefix(self.interface) schema_info = self.get_schema_info(pref) schema_info.types[tn] = node def add_complex_type(self, cls, node): tn = cls.get_type_name() pref = cls.get_namespace_prefix(self.interface) schema_info = self.get_schema_info(pref) schema_info.types[tn] = node def get_schema_info(self, prefix): """Returns the SchemaInfo object for the corresponding namespace. It creates it if it doesn't exist. The SchemaInfo object holds the simple and complex type definitions for a given namespace.""" if prefix in self.namespaces: schema = self.namespaces[prefix] else: schema = self.namespaces[prefix] = SchemaInfo() return schema spyne-spyne-2.14.0/spyne/interface/xml_schema/defn.py000066400000000000000000000143621417664205300226120ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.util.six import add_metaclass from spyne.const import xml from spyne.model.primitive import Boolean, AnyHtml from spyne.model.primitive import Unicode from spyne.model.primitive import UnsignedInteger from spyne.model.complex import XmlAttribute from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModelMeta @add_metaclass(ComplexModelMeta) class SchemaBase(ComplexModelBase): __namespace__ = xml.NS_XSD class Import(SchemaBase): namespace = XmlAttribute(Unicode) class Element(SchemaBase): name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) ref = XmlAttribute(Unicode) # it can be "unbounded", so it should be of type Unicode max_occurs = XmlAttribute(Unicode(default="1", sub_name="maxOccurs")) # Also Unicode for consistency with max_occurs min_occurs = XmlAttribute(Unicode(default="1", sub_name="minOccurs")) nillable = XmlAttribute(Boolean(default=False)) default = XmlAttribute(Unicode) class IntegerAttribute(SchemaBase): value = XmlAttribute(UnsignedInteger) class StringAttribute(SchemaBase): value = XmlAttribute(Unicode) class List(SchemaBase): _type_info = [ ('item_type', XmlAttribute(Unicode(sub_name='itemType'))), ] class SimpleType(SchemaBase): _type_info = [ ('name', XmlAttribute(Unicode)), ('list', List), ('union', Unicode), ] class Attribute(SchemaBase): use = XmlAttribute(Unicode) ref = XmlAttribute(Unicode) name = XmlAttribute(Unicode) type = XmlAttribute(Unicode) default = XmlAttribute(Unicode) simple_type = SimpleType.customize(sub_name='simpleType') class Restriction(SchemaBase): _type_info = [ ('base', XmlAttribute(Unicode)), ('max_length', IntegerAttribute.customize(sub_name="maxLength")), ('min_length', IntegerAttribute.customize(sub_name="minLength")), ('pattern', StringAttribute), ('enumeration', StringAttribute.customize(max_occurs="unbounded")), ('attributes', Attribute.customize(max_occurs="unbounded", sub_name="attribute")), ] SimpleType.append_field('restriction', Restriction) class Choice(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") class Sequence(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") choices = Choice.customize(max_occurs="unbounded", sub_name="choice") class Extension(SchemaBase): base = XmlAttribute(Unicode) attributes = Attribute.customize(max_occurs="unbounded", sub_name="attribute") class SimpleContent(SchemaBase): extension = Extension restriction = Restriction class ComplexType(SchemaBase): name = XmlAttribute(Unicode) sequence = Sequence simple_content = SimpleContent.customize(sub_name="simpleContent") attributes = Attribute.customize(max_occurs="unbounded", sub_name="attribute") choice = Choice class Include(SchemaBase): schema_location = XmlAttribute(Unicode(sub_name="schemaLocation")) class XmlSchema10(SchemaBase): _type_info = [ ('target_namespace', XmlAttribute(Unicode(sub_name="targetNamespace"))), ('element_form_default', XmlAttribute(Unicode( sub_name="elementFormDefault"))), ('imports', Import.customize(max_occurs="unbounded", sub_name="import")), ('includes', Include.customize(max_occurs="unbounded", sub_name="include")), ('elements', Element.customize(max_occurs="unbounded", sub_name="element")), ('simple_types', SimpleType.customize(max_occurs="unbounded", sub_name="simpleType")), ('complex_types', ComplexType.customize(max_occurs="unbounded", sub_name="complexType")), ('attributes', Attribute.customize(max_occurs="unbounded", sub_name="attribute")), ] from itertools import chain from inspect import isclass from spyne.model import ModelBase from spyne.model import primitive from spyne.model import binary from spyne.model.fault import Fault TYPE_MAP = dict([ ("{%s}%s" % (cls.get_namespace(), cls.get_type_name()), cls) for cls in chain( [v for v in vars(primitive).values() if getattr(v, '__type_name__', None) is not None], [ binary.ByteArray(encoding='base64'), binary.ByteArray(encoding='hex'), ], [ primitive.Point(2), primitive.Point(3), primitive.Line(2), primitive.Line(3), primitive.Polygon(2), primitive.Polygon(3), primitive.MultiPoint(2), primitive.MultiPoint(3), primitive.MultiLine(2), primitive.MultiLine(3), primitive.MultiPolygon(2), primitive.MultiPolygon(3), ] ) if isclass(cls) and issubclass(cls, ModelBase) and not issubclass(cls, (Fault, AnyHtml)) and not cls in (ModelBase,) ]) if __name__ == '__main__': from pprint import pprint pprint(TYPE_MAP) spyne-spyne-2.14.0/spyne/interface/xml_schema/genpy.py000066400000000000000000000102321417664205300230100ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ A barely functional Spyne class serializer. If you're using this as part of anything serious, you're insane. TODO: - Customizations are not serialized. """ import logging logger = logging.getLogger(__name__) import spyne from datetime import datetime from itertools import chain from collections import defaultdict from spyne.model import SimpleModel from spyne.model.complex import XmlModifier from spyne.model.complex import ComplexModelBase def gen_fn_from_tns(tns): return tns \ .replace('http://', '') \ .replace('https://', '') \ .replace('/', '') \ .replace('.', '_') \ .replace(':', '_') \ .replace('#', '') \ .replace('-', '_') class CodeGenerator(object): def __init__(self, fn_tns_mapper=gen_fn_from_tns): self.imports = set() self.classes = set() self.pending = defaultdict(list) self.simples = set() self.fn_tns_mapper = fn_tns_mapper def gen_modifier(self, t): return '%s(%s)' % (t.__name__, self.gen_dispatch(t.type)) def gen_simple(self, t): return t.__name__ def gen_complex(self, t): retval = [] retval.append(""" class %s(_ComplexBase): _type_info = [""" % (t.get_type_name())) for k,v in t._type_info.items(): if not issubclass(v, ComplexModelBase) or \ v.get_namespace() != self.tns or \ v in self.classes or \ getattr(v, '__orig__', None) in self.classes: retval.append(" ('%s', %s)," % (k, self.gen_dispatch(v))) else: self.pending[v.get_type_name()].append((k, t.get_type_name())) retval.append(" ]") self.classes.add(t) for k,orig_t in self.pending[t.get_type_name()]: retval.append('%s._type_info["%s"] = %s' % (orig_t, k, t.get_type_name())) return retval def gen_dispatch(self, t): if issubclass(t, XmlModifier): return self.gen_modifier(t) if issubclass(t, SimpleModel): return self.gen_simple(t) if t.get_namespace() == self.tns: return t.get_type_name() i = self.fn_tns_mapper(t.get_namespace()) self.imports.add(i) return "%s.%s" % (i, t.get_type_name()) def genpy(self, tns, s): self.tns = tns retval = [u"""# encoding: utf8 # Automatically generated by Spyne %s at %s. # Modify at your own risk. from spyne.model import * """ % (spyne.__version__, datetime.now().replace(microsecond=0).isoformat(' ')), "", # imports """ class _ComplexBase(ComplexModelBase): __namespace__ = '%s' __metaclass__ = ComplexModelMeta""" % tns ] for n, t in s.types.items(): if issubclass(t, ComplexModelBase): retval.extend(self.gen_complex(t)) else: retval.append('%s = %s' % (n, self.gen_dispatch(t))) self.simples.add(n) for i in self.imports: retval.insert(1, "import %s" % i) retval.append("") retval.append("") retval.append('__all__ = [') for c in sorted(chain([c.get_type_name() for c in self.classes], self.simples)): retval.append(" '%s'," % c) retval.append(']') retval.append("") return '\n'.join(retval) spyne-spyne-2.14.0/spyne/interface/xml_schema/model.py000066400000000000000000000334541417664205300230010ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.interface.xml_schema.model`` module contains type-specific logic for schema generation.""" import logging logger = logging.getLogger(__name__) from decimal import Decimal as D from collections import deque, defaultdict from lxml import etree from spyne.model import ModelBase, XmlAttribute, AnyXml, Unicode, XmlData, \ Decimal, Integer from spyne.const.xml import NS_XSD, XSD from spyne.util import memoize from spyne.util.cdict import cdict from spyne.util.etreeconv import dict_to_etree from spyne.util.six import string_types from spyne.protocol.xml import XmlDocument _prot = XmlDocument() # In Xml Schema, some customizations do not need a class to be extended -- they # are specified in-line in the parent class definition, like nullable or # min_occurs. The dict below contains list of parameters that do warrant a # proper subclass definition for each type. This must be updated as the Xml # Schema implementation makes progress. ATTR_NAMES = cdict({ ModelBase: set(['values']), Decimal: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits', 'fraction_digits']), Integer: set(['pattern', 'gt', 'ge', 'lt', 'le', 'values', 'total_digits']), Unicode: set(['values', 'min_len', 'max_len', 'pattern']), }) def xml_attribute_add(cls, name, element, document): element.set('name', name) element.set('type', cls.type.get_type_name_ns(document.interface)) if cls._use is not None: element.set('use', cls._use) d = cls.type.Attributes.default if d is not None: element.set('default', _prot.to_unicode(cls.type, d)) def _check_extension_attrs(cls): """Make sure only customizations that need a restriction tag generate one""" extends = cls.__extends__ eattrs = extends.Attributes cattrs = cls.Attributes ckeys = set([k for k in vars(cls.Attributes) if not k.startswith('_')]) ekeys = set([k for k in vars(extends.Attributes) if not k.startswith('_')]) # get the attributes different from the parent class diff = set() for k in (ckeys | ekeys): if getattr(eattrs, k, None) != getattr(cattrs, k, None): diff.add(k) # compare them with what comes from ATTR_NAMES attr_names = ATTR_NAMES[cls] retval = None while extends is not None: retval = extends if len(diff & attr_names) > 0: return extends extends = extends.__extends__ return retval # noinspection PyDefaultArgument def simple_get_restriction_tag(document, cls): extends = _check_extension_attrs(cls) if extends is None: return simple_type = etree.Element(XSD('simpleType')) simple_type.set('name', cls.get_type_name()) document.add_simple_type(cls, simple_type) restriction = etree.SubElement(simple_type, XSD('restriction')) restriction.set('base', extends.get_type_name_ns(document.interface)) for v in cls.Attributes.values: enumeration = etree.SubElement(restriction, XSD('enumeration')) enumeration.set('value', XmlDocument().to_unicode(cls, v)) return restriction def simple_add(document, cls, tags): if not cls.is_default(cls): document.get_restriction_tag(cls) def byte_array_add(document, cls, tags): simple_add(document, cls, tags) def complex_add(document, cls, tags): complex_type = etree.Element(XSD('complexType')) complex_type.set('name', cls.get_type_name()) doc_text = cls.get_documentation() if doc_text or cls.Annotations.appinfo is not None: annotation = etree.SubElement(complex_type, XSD('annotation')) if doc_text: doc = etree.SubElement(annotation, XSD('documentation')) doc.text = doc_text _ai = cls.Annotations.appinfo if _ai is not None: appinfo = etree.SubElement(annotation, XSD('appinfo')) if isinstance(_ai, dict): dict_to_etree(_ai, appinfo) elif isinstance(_ai, string_types): appinfo.text = _ai elif isinstance(_ai, etree._Element): appinfo.append(_ai) else: from spyne.util.xml import get_object_as_xml appinfo.append(get_object_as_xml(_ai)) sequence_parent = complex_type extends = getattr(cls, '__extends__', None) type_info = cls._type_info if extends is not None: if (extends.get_type_name() == cls.get_type_name() and extends.get_namespace() == cls.get_namespace()): raise Exception("%r can't extend %r because they are both '{%s}%s'" % (cls, extends, cls.get_namespace(), cls.get_type_name())) if extends.Attributes.exc_interface: # If the parent class is private, it won't be in the schema, so we # need to act as if its attributes are part of cls as well. type_info = cls.get_simple_type_info(cls) else: complex_content = etree.SubElement(complex_type, XSD('complexContent')) extension = etree.SubElement(complex_content, XSD('extension')) extension.set('base', extends.get_type_name_ns(document.interface)) sequence_parent = extension if cls.Attributes._xml_tag_body_as is not None: for xtba_key, xtba_type in cls.Attributes._xml_tag_body_as: _sc = etree.SubElement(sequence_parent, XSD('simpleContent')) xtba_ext = etree.SubElement(_sc, XSD('extension')) xtba_ext.attrib['base'] = xtba_type.type.get_type_name_ns( document.interface) sequence = etree.Element(XSD('sequence')) deferred = deque() choice_tags = defaultdict(lambda: etree.Element(XSD('choice'))) for k, v in type_info.items(): assert isinstance(k, string_types) assert issubclass(v, ModelBase) a = v.Attributes if a.exc_interface: continue if issubclass(v, XmlData): continue if issubclass(v, XmlAttribute): deferred.append((k,v)) continue document.add(v, tags) name = a.sub_name if name is None: name = k #ns = a.sub_ns #if ns is not None: # name = "{%s}%s" % (ns, name) type_name_ns = v.get_type_name_ns(document.interface) if v.__extends__ is not None and v.__orig__ is not None and \ _check_extension_attrs(v) is None: type_name_ns = v.__orig__.get_type_name_ns(document.interface) member = etree.Element(a.schema_tag) if a.schema_tag == XSD('element'): member.set('name', name) member.set('type', type_name_ns) elif a.schema_tag == XSD('any') and issubclass(v, AnyXml): if a.namespace is not None: member.set('namespace', a.namespace) if a.process_contents is not None: member.set('processContents', a.process_contents) else: raise ValueError("Unhandled schema_tag / type combination. %r %r" % (v, a.schema_tag)) if a.min_occurs != 1: # 1 is the xml schema default member.set('minOccurs', str(a.min_occurs)) if a.max_occurs != 1: # 1 is the xml schema default val = a.max_occurs if val in (D('inf'), float('inf')): val = 'unbounded' else: val = str(val) member.set('maxOccurs', val) if a.default is not None: member.set('default', _prot.to_unicode(v, a.default)) if bool(a.nillable) != False: # False is the xml schema default member.set('nillable', 'true') v_doc_text = v.get_documentation() if v_doc_text: # Doesn't support multi-language documentation annotation = etree.SubElement(member, XSD('annotation')) doc = etree.SubElement(annotation, XSD('documentation')) doc.text = v_doc_text if a.xml_choice_group is None: sequence.append(member) else: choice_tags[a.xml_choice_group].append(member) sequence.extend(choice_tags.values()) if len(sequence) > 0: sequence_parent.append(sequence) _ext_elements = dict() for k,v in deferred: attribute = etree.Element(XSD('attribute')) xml_attribute_add(v, k, attribute, document) if cls.Attributes._xml_tag_body_as is None: sequence_parent.append(attribute) else: xtba_ext.append(attribute) document.add_complex_type(cls, complex_type) # simple node complex_type_name = cls.Attributes.sub_name or cls.get_type_name() element = etree.Element(XSD('element')) element.set('name', complex_type_name) element.set('type', cls.get_type_name_ns(document.interface)) document.add_element(cls, element) def enum_add(document, cls, tags): simple_type = etree.Element(XSD('simpleType')) simple_type.set('name', cls.get_type_name()) restriction = etree.SubElement(simple_type, XSD('restriction')) restriction.set('base', '%s:string' % document.interface.get_namespace_prefix(NS_XSD)) for v in cls.__values__: enumeration = etree.SubElement(restriction, XSD('enumeration')) enumeration.set('value', v) document.add_simple_type(cls, simple_type) fault_add = complex_add def unicode_get_restriction_tag(document, cls): restriction = simple_get_restriction_tag(document, cls) if restriction is None: return # length if cls.Attributes.min_len == cls.Attributes.max_len: length = etree.SubElement(restriction, XSD('length')) length.set('value', str(cls.Attributes.min_len)) else: if cls.Attributes.min_len != Unicode.Attributes.min_len: min_l = etree.SubElement(restriction, XSD('minLength')) min_l.set('value', str(cls.Attributes.min_len)) if cls.Attributes.max_len != Unicode.Attributes.max_len: max_l = etree.SubElement(restriction, XSD('maxLength')) max_l.set('value', str(cls.Attributes.max_len)) # pattern if cls.Attributes.pattern != Unicode.Attributes.pattern: pattern = etree.SubElement(restriction, XSD('pattern')) pattern.set('value', cls.Attributes.pattern) return restriction prot = XmlDocument() def Tget_range_restriction_tag(T): """The get_range_restriction template function. Takes a primitive, returns a function that generates range restriction tags. """ from spyne.model.primitive import Decimal from spyne.model.primitive import Integer if issubclass(T, Decimal): def _get_float_restrictions(prot, restriction, cls): if cls.Attributes.fraction_digits != T.Attributes.fraction_digits: elt = etree.SubElement(restriction, XSD('fractionDigits')) elt.set('value', prot.to_unicode(cls, cls.Attributes.fraction_digits)) def _get_integer_restrictions(prot, restriction, cls): if cls.Attributes.total_digits != T.Attributes.total_digits: elt = etree.SubElement(restriction, XSD('totalDigits')) elt.set('value', prot.to_unicode(cls, cls.Attributes.total_digits)) if issubclass(T, Integer): def _get_additional_restrictions(prot, restriction, cls): _get_integer_restrictions(prot, restriction, cls) else: def _get_additional_restrictions(prot, restriction, cls): _get_integer_restrictions(prot, restriction, cls) _get_float_restrictions(prot, restriction, cls) else: def _get_additional_restrictions(prot, restriction, cls): pass def _get_range_restriction_tag(document, cls): restriction = simple_get_restriction_tag(document, cls) if restriction is None: return if cls.Attributes.gt != T.Attributes.gt: elt = etree.SubElement(restriction, XSD('minExclusive')) elt.set('value', prot.to_unicode(cls, cls.Attributes.gt)) if cls.Attributes.ge != T.Attributes.ge: elt = etree.SubElement(restriction, XSD('minInclusive')) elt.set('value', prot.to_unicode(cls, cls.Attributes.ge)) if cls.Attributes.lt != T.Attributes.lt: elt = etree.SubElement(restriction, XSD('maxExclusive')) elt.set('value', prot.to_unicode(cls, cls.Attributes.lt)) if cls.Attributes.le != T.Attributes.le: elt = etree.SubElement(restriction, XSD('maxInclusive')) elt.set('value', prot.to_unicode(cls, cls.Attributes.le)) if cls.Attributes.pattern != T.Attributes.pattern: elt = etree.SubElement(restriction, XSD('pattern')) elt.set('value', cls.Attributes.pattern) _get_additional_restrictions(prot, restriction, cls) return restriction return _get_range_restriction_tag spyne-spyne-2.14.0/spyne/interface/xml_schema/parser.py000066400000000000000000000557151417664205300232010ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # To see the list of xml schema builtins recognized by this parser, run defn.py # in this package. # This module is EXPERIMENTAL. Only a subset of Xml schema standard is # implemented. # import logging logger = logging.getLogger(__name__) import os from copy import copy from pprint import pformat from itertools import chain from collections import defaultdict from os.path import dirname, abspath, join from lxml import etree from spyne.util import memoize from spyne.util.odict import odict from spyne.model import Null, XmlData, XmlAttribute, Array, ComplexModelBase, \ ComplexModelMeta from spyne.model.complex import XmlModifier from spyne.protocol.xml import XmlDocument from spyne.interface.xml_schema.defn import TYPE_MAP from spyne.interface.xml_schema.defn import SchemaBase from spyne.interface.xml_schema.defn import XmlSchema10 from spyne.util.color import R, G, B, MAG, YEL PARSER = etree.XMLParser(remove_comments=True) _prot = XmlDocument() class _Schema(object): def __init__(self): self.types = {} self.elements = {} self.imports = set() # FIXME: Needs to emit delayed assignment of recursive structures instead of # lousy ellipses. def Thier_repr(with_ns=False): """Template for ``hier_repr``, a ``repr`` variant that shows spyne ``ComplexModel``s in a hierarchical format. :param with_ns: either bool or a callable that returns the class name as string """ if with_ns is False: def get_class_name(c): return c.get_type_name() elif with_ns is True or with_ns == 1: def get_class_name(c): return "{%s}%s" % (c.get_namespace(), c.get_type_name()) else: def get_class_name(c): return with_ns(c.get_namespace(), c.get_type_name()) def hier_repr(inst, i0=0, I=' ', tags=None): if tags is None: tags = set() cls = inst.__class__ if not hasattr(cls, '_type_info'): return repr(inst) clsid = "%s" % (get_class_name(cls)) if id(inst) in tags: return clsid tags.add(id(inst)) i1 = i0 + 1 i2 = i1 + 1 retval = [clsid, '('] xtba = cls.Attributes._xml_tag_body_as if xtba is not None: xtba = iter(xtba) xtba_key, xtba_type = next(xtba) if xtba_key is not None: value = getattr(inst, xtba_key, None) retval.append("%s,\n" % hier_repr(value, i1, I, tags)) else: retval.append('\n') else: retval.append('\n') for k, v in inst.get_flat_type_info(cls).items(): value = getattr(inst, k, None) if (issubclass(v, Array) or v.Attributes.max_occurs > 1) and \ value is not None: retval.append("%s%s=[\n" % (I * i1, k)) for subval in value: retval.append("%s%s,\n" % (I * i2, hier_repr(subval, i2, I, tags))) retval.append('%s],\n' % (I * i1)) elif issubclass(v, XmlData): pass else: retval.append("%s%s=%s,\n" % (I * i1, k, hier_repr(value, i1, I, tags))) retval.append('%s)' % (I * i0)) return ''.join(retval) return hier_repr SchemaBase.__repr__ = Thier_repr() hier_repr = Thier_repr() hier_repr_ns = Thier_repr(with_ns=True) class XmlSchemaParser(object): def __init__(self, files, base_dir=None, repr_=Thier_repr(with_ns=False), skip_errors=False): self.retval = {} self.indent = 0 self.files = files self.base_dir = base_dir self.repr = repr_ if self.base_dir is None: self.base_dir = os.getcwd() self.parent = None self.children = None self.nsmap = None self.schema = None self.prefmap = None self.tns = None self.pending_elements = None self.pending_types = None self.skip_errors = skip_errors self.pending_simple_types = defaultdict(set) def clone(self, indent=0, base_dir=None): retval = copy(self) if retval.parent is None: retval.parent = self if self.children is None: self.children = [retval] else: self.children.append(retval) else: retval.parent.children.append(retval) retval.indent = self.indent + indent if base_dir is not None: retval.base_dir = base_dir return retval def debug0(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * self.indent, s), *args, **kwargs) def debug1(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * (self.indent + 1), s), *args, **kwargs) def debug2(self, s, *args, **kwargs): logger.debug("%s%s" % (" " * (self.indent + 2), s), *args, **kwargs) def parse_schema_file(self, file_name): elt = etree.fromstring(open(file_name, 'rb').read(), parser=PARSER) return self.parse_schema(elt) def process_includes(self, include): file_name = include.schema_location if file_name is None: return self.debug1("including %s %s", self.base_dir, file_name) file_name = abspath(join(self.base_dir, file_name)) data = open(file_name, 'rb').read() elt = etree.fromstring(data, parser=PARSER) self.nsmap.update(elt.nsmap) self.prefmap = dict([(v, k) for k, v in self.nsmap.items()]) sub_schema = _prot.from_element(None, XmlSchema10, elt) if sub_schema.includes: for inc in sub_schema.includes: base_dir = dirname(file_name) child_ctx = self.clone(base_dir=base_dir) self.process_includes(inc) self.nsmap.update(child_ctx.nsmap) self.prefmap = dict([(v, k) for k, v in self.nsmap.items()]) for attr in ('imports', 'simple_types', 'complex_types', 'elements'): sub = getattr(sub_schema, attr) if sub is None: sub = [] own = getattr(self.schema, attr) if own is None: own = [] own.extend(sub) setattr(self.schema, attr, own) def process_simple_type_list(self, s, name=None): item_type = s.list.item_type if item_type is None: self.debug1("skipping simple type: %s because its list itemType " "could not be found", name) return base = self.get_type(item_type) if base is None: self.pending_simple_types[self.get_name(item_type)].add((s, name)) self.debug1("pending simple type list: %s " "because of unseen base %s", name, item_type) return self.debug1("adding simple type list: %s", name) retval = Array(base, serialize_as='sd-list') # FIXME: to be implemented retval.__type_name__ = name retval.__namespace__ = self.tns assert not retval.get_type_name() is retval.Empty return retval def process_simple_type_restriction(self, s, name=None): base_name = s.restriction.base if base_name is None: self.debug1("skipping simple type: %s because its restriction base " "could not be found", name) return base = self.get_type(base_name) if base is None: self.pending_simple_types[self.get_name(base_name)].add((s, name)) self.debug1("pending simple type: %s because of unseen base %s", name, base_name) return self.debug1("adding simple type: %s", name) kwargs = {} restriction = s.restriction if restriction.enumeration: kwargs['values'] = [e.value for e in restriction.enumeration] if restriction.max_length: if restriction.max_length.value: kwargs['max_len'] = int(restriction.max_length.value) if restriction.min_length: if restriction.min_length.value: kwargs['min_len'] = int(restriction.min_length.value) if restriction.pattern: if restriction.pattern.value: kwargs['pattern'] = restriction.pattern.value retval = base.customize(**kwargs) retval.__type_name__ = name retval.__namespace__ = self.tns if retval.__orig__ is None: retval.__orig__ = base if retval.__extends__ is None: retval.__extends__ = base assert not retval.get_type_name() is retval.Empty return retval def process_simple_type_union(self, s, name=None): self.debug1("skipping simple type: %s because is not " "implemented", name) def process_simple_type(self, s, name=None): """Returns the simple Spyne type from `` tag.""" retval = None if name is None: name = s.name if s.list is not None: retval = self.process_simple_type_list(s, name) elif s.union is not None: retval = self.process_simple_type_union(s, name) elif s.restriction is not None: retval = self.process_simple_type_restriction(s, name) if retval is None: self.debug1("skipping simple type: %s", name) return self.retval[self.tns].types[s.name] = retval key = self.get_name(name) dependents = self.pending_simple_types[key] for s, name in set(dependents): st = self.process_simple_type(s, name) if st is not None: self.retval[self.tns].types[s.name] = st self.debug2("added back simple type: %s", s.name) dependents.remove((s, name)) if len(dependents) == 0: del self.pending_simple_types[key] return retval def process_schema_element(self, e): if e.name is None: return self.debug1("adding element: %s", e.name) t = self.get_type(e.type) if t: if e.name in self.pending_elements: del self.pending_elements[e.name] self.retval[self.tns].elements[e.name] = e else: self.pending_elements[e.name] = e def process_attribute(self, a): if a.ref is not None: t = self.get_type(a.ref) return t.type.get_type_name(), t if a.type is not None and a.simple_type is not None: raise ValueError(a, "Both type and simple_type are defined.") elif a.type is not None: t = self.get_type(a.type) if t is None: raise ValueError(a, 'type %r not found' % a.type) elif a.simple_type is not None: t = self.process_simple_type(a.simple_type, a.name) if t is None: raise ValueError(a, 'simple type %r not found' % a.simple_type) else: raise Exception("dunno attr") kwargs = {} if a.default is not None: kwargs['default'] = _prot.from_unicode(t, a.default) if len(kwargs) > 0: t = t.customize(**kwargs) self.debug2("t = t.customize(**%r)" % kwargs) return a.name, XmlAttribute(t) def process_complex_type(self, c): def process_type(tn, name, wrapper=None, element=None, attribute=None): if wrapper is None: wrapper = lambda x: x else: assert issubclass(wrapper, XmlModifier), wrapper t = self.get_type(tn) key = (c.name, name) if t is None: self.pending_types[key] = c self.debug2("not found: %r(%s)", key, tn) return if key in self.pending_types: del self.pending_types[key] assert name is not None, (key, e) kwargs = {} if element is not None: if e.min_occurs != "0": # spyne default kwargs['min_occurs'] = int(e.min_occurs) if e.max_occurs == "unbounded": kwargs['max_occurs'] = e.max_occurs elif e.max_occurs != "1": kwargs['max_occurs'] = int(e.max_occurs) if e.nillable != True: # spyne default kwargs['nillable'] = e.nillable if e.default is not None: kwargs['default'] = _prot.from_unicode(t, e.default) if len(kwargs) > 0: t = t.customize(**kwargs) if attribute is not None: if attribute.default is not None: kwargs['default'] = _prot.from_unicode(t, a.default) if len(kwargs) > 0: t = t.customize(**kwargs) ti.append( (name, wrapper(t)) ) self.debug2(" found: %r(%s), c: %r", key, tn, kwargs) def process_element(e): if e.ref is not None: tn = e.ref name = e.ref.split(":", 1)[-1] elif e.name is not None: tn = e.type name = e.name if tn is None: # According to http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-element # this means this element is now considered to be a # http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#ur-type-itself self.debug2(" skipped: %s ur-type", e.name) return else: raise Exception("dunno") process_type(tn, name, element=e) ti = [] base = ComplexModelBase if c.name in self.retval[self.tns].types: self.debug1("modifying existing %r", c.name) else: self.debug1("adding complex type: %s", c.name) if c.sequence is not None: if c.sequence.elements is not None: for e in c.sequence.elements: process_element(e) if c.sequence.choices is not None: for ch in c.sequence.choices: if ch.elements is not None: for e in ch.elements: process_element(e) if c.choice is not None: if c.choice.elements is not None: for e in c.choice.elements: process_element(e) if c.attributes is not None: for a in c.attributes: if a.name is None: continue if a.type is None: continue process_type(a.type, a.name, XmlAttribute, attribute=a) if c.simple_content is not None: sc = c.simple_content ext = sc.extension restr = sc.restriction if ext is not None: base_name = ext.base b = self.get_type(ext.base) if ext.attributes is not None: for a in ext.attributes: ti.append(self.process_attribute(a)) elif restr is not None: base_name = restr.base b = self.get_type(restr.base) if restr.attributes is not None: for a in restr.attributes: ti.append(self.process_attribute(a)) else: raise Exception("Invalid simpleContent tag: %r", sc) if issubclass(b, ComplexModelBase): base = b else: process_type(base_name, "_data", XmlData) if c.name in self.retval[self.tns].types: r = self.retval[self.tns].types[c.name] r._type_info.update(ti) else: cls_dict = odict({ '__type_name__': c.name, '__namespace__': self.tns, '_type_info': ti, }) if self.repr is not None: cls_dict['__repr__'] = self.repr r = ComplexModelMeta(str(c.name), (base,), cls_dict) self.retval[self.tns].types[c.name] = r return r def get_name(self, tn): if tn.startswith("{"): ns, qn = tn[1:].split('}', 1) elif ":" in tn: ns, qn = tn.split(":", 1) ns = self.nsmap[ns] else: if None in self.nsmap: ns, qn = self.nsmap[None], tn else: ns, qn = self.tns, tn return ns, qn def get_type(self, tn): if tn is None: return Null ns, qn = self.get_name(tn) ti = self.retval.get(ns) if ti is not None: t = ti.types.get(qn) if t: return t e = ti.elements.get(qn) if e: if e.type and ":" in e.type: return self.get_type(e.type) else: retval = self.get_type("{%s}%s" % (ns, e.type)) if retval is None and None in self.nsmap: retval = self.get_type("{%s}%s" % (self.nsmap[None], e.type)) return retval return TYPE_MAP.get("{%s}%s" % (ns, qn)) def process_pending(self): # process pending self.debug0("6 %s processing pending complex_types", B(self.tns)) for (c_name, e_name), _v in list(self.pending_types.items()): self.process_complex_type(_v) self.debug0("7 %s processing pending elements", YEL(self.tns)) for _k, _v in self.pending_elements.items(): self.process_schema_element(_v) def print_pending(self, fail=False): ptt_pending = sum((len(v) for v in self.pending_simple_types.values())) > 0 if len(self.pending_elements) > 0 or len(self.pending_types) > 0 or \ ptt_pending: if fail: logging.basicConfig(level=logging.DEBUG) self.debug0("%" * 50) self.debug0(self.tns) self.debug0("") self.debug0("elements") self.debug0(pformat(self.pending_elements)) self.debug0("") self.debug0("simple types") self.debug0(pformat(self.pending_simple_types)) self.debug0("%" * 50) self.debug0("complex types") self.debug0(pformat(self.pending_types)) self.debug0("%" * 50) if fail: raise Exception("there are still unresolved elements") def parse_schema(self, elt): self.nsmap = dict(elt.nsmap.items()) self.prefmap = dict([(v, k) for k, v in self.nsmap.items()]) self.schema = schema = _prot.from_element(self, XmlSchema10, elt) self.pending_types = {} self.pending_elements = {} self.tns = tns = schema.target_namespace if self.tns is None: self.tns = tns = '__no_ns__' if tns in self.retval: return self.retval[tns] = _Schema() self.debug0("1 %s processing includes", MAG(tns)) if schema.includes: for include in schema.includes: self.process_includes(include) if schema.elements: schema.elements = odict([(e.name, e) for e in schema.elements]) if schema.complex_types: schema.complex_types = odict([(c.name, c) for c in schema.complex_types]) if schema.simple_types: schema.simple_types = odict([(s.name, s) for s in schema.simple_types]) if schema.attributes: schema.attributes = odict([(a.name, a) for a in schema.attributes]) self.debug0("2 %s processing imports", R(tns)) if schema.imports: for imp in schema.imports: if not imp.namespace in self.retval: self.debug1("%s importing %s", tns, imp.namespace) fname = self.files[imp.namespace] self.clone(2, dirname(fname)).parse_schema_file(fname) self.retval[tns].imports.add(imp.namespace) self.debug0("3 %s processing simple_types", G(tns)) if schema.simple_types: for s in schema.simple_types.values(): self.process_simple_type(s) # no simple types should have been left behind. assert sum((len(v) for v in self.pending_simple_types.values())) == 0, \ self.pending_simple_types.values() self.debug0("4 %s processing attributes", G(tns)) if schema.attributes: for s in schema.attributes.values(): n, t = self.process_attribute(s) self.retval[self.tns].types[n] = t self.debug0("5 %s processing complex_types", B(tns)) if schema.complex_types: for c in schema.complex_types.values(): self.process_complex_type(c) self.debug0("6 %s processing elements", YEL(tns)) if schema.elements: for e in schema.elements.values(): self.process_schema_element(e) self.process_pending() if self.parent is None: # for the top-most schema if self.children is not None: # if it uses or # This is needed for schemas with circular imports for c in chain([self], self.children): c.print_pending() self.debug0('') # FIXME: should put this in a while loop that loops until no # changes occur for c in chain([self], self.children): c.process_pending() for c in chain([self], self.children): c.process_pending() self.debug0('') for c in chain([self], self.children): c.print_pending(fail=(not self.skip_errors)) return self.retval spyne-spyne-2.14.0/spyne/model/000077500000000000000000000000001417664205300163365ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/model/__init__.py000066400000000000000000000043111417664205300204460ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.model`` package contains data types that Spyne is able to distinguish. These are just type markers, they are not of much use without protocols. """ from spyne.model._base import Ignored from spyne.model._base import ModelBase from spyne.model._base import PushBase from spyne.model._base import Null from spyne.model._base import SimpleModel # Primitives from spyne.model.primitive import * # store_as values # it's sad that xml the pssm and xml the module conflict. that's why we need # this after import of primitive package from spyne.model._base import xml from spyne.model._base import json from spyne.model._base import jsonb from spyne.model._base import table from spyne.model._base import msgpack # Classes from spyne.model.complex import ComplexModelMeta from spyne.model.complex import ComplexModelBase from spyne.model.complex import ComplexModel from spyne.model.complex import TTableModelBase from spyne.model.complex import TTableModel # Iterables from spyne.model.complex import Array from spyne.model.complex import Iterable from spyne.model.complex import PushBase # Modifiers from spyne.model.complex import Mandatory from spyne.model.complex import XmlAttribute from spyne.model.complex import XmlData # Markers from spyne.model.complex import SelfReference # Binary from spyne.model.binary import File from spyne.model.binary import ByteArray # Enum from spyne.model.enum import Enum # Fault from spyne.model.fault import Fault spyne-spyne-2.14.0/spyne/model/_base.py000066400000000000000000001126111417664205300177630ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """This module contains the ModelBase class and other building blocks for defining models. """ from __future__ import print_function import logging logger = logging.getLogger(__name__) import re import decimal import threading import spyne.const.xml from copy import deepcopy from collections import OrderedDict from spyne import const from spyne.util import Break, six from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.const.xml import DEFAULT_NS class Ignored(object): """When returned as a real rpc response, this is equivalent to returning None. However, direct method invocations (and NullServer) get the return value. It can be used for tests and from hooks.""" __slots__ = ('args', 'kwargs') def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def __eq__(self, other): return isinstance(other, Ignored) \ and self.args == other.args and self.kwargs == other.kwargs def __ne__(self, other): return not (isinstance(other, Ignored) \ and self.args == other.args and self.kwargs == other.kwargs) def _decode_pa_dict(d): """Decodes dict passed to prot_attrs. >>> _decode_pa_dict({}) cdict({}) >>> _decode_pa_dict({1: 2)}) cdict({1: 2}) >>> _decode_pa_dict({(1,2): 3)}) cdict({1: 3, 2: 3}) """ retval = cdict() for k, v in d.items(): if isinstance(k, (frozenset, tuple)): for subk in k: retval[subk] = v for k, v in d.items(): if not isinstance(k, (frozenset, tuple)): retval[k] = v return retval class AttributesMeta(type(object)): NULLABLE_DEFAULT = True def __new__(cls, cls_name, cls_bases, cls_dict): # Mapper args should not be inherited. if not 'sqla_mapper_args' in cls_dict: cls_dict['sqla_mapper_args'] = None rd = {} for k in list(cls_dict.keys()): if k in ('parser', 'cast'): rd['parser'] = cls_dict.pop(k) continue if k in ('sanitize', 'sanitizer'): rd['sanitizer'] = cls_dict.pop(k) continue if k == 'logged': rd['logged'] = cls_dict.pop(k) continue retval = super(AttributesMeta, cls).__new__(cls, cls_name, cls_bases, cls_dict) for k, v in rd.items(): if v is None: setattr(retval, k, None) else: setattr(retval, k, staticmethod(v)) return retval def __init__(self, cls_name, cls_bases, cls_dict): # you will probably want to look at ModelBase._s_customize as well. if not hasattr(self, '_method_config_do'): self._method_config_do = None nullable = cls_dict.get('nullable', None) nillable = cls_dict.get('nillable', None) if nullable is not None: assert nillable is None or nullable == nillable self._nullable = nullable elif nillable is not None: assert nullable is None or nullable == nillable self._nullable = nillable if not hasattr(self, '_nullable'): self._nullable = None if not hasattr(self, '_default_factory'): self._default_factory = None if not hasattr(self, '_html_cloth'): self._html_cloth = None if not hasattr(self, '_html_root_cloth'): self._html_root_cloth = None if 'html_cloth' in cls_dict: self.set_html_cloth(cls_dict.pop('html_cloth')) if 'html_root_cloth' in cls_dict: self.set_html_cloth(cls_dict.pop('html_root_cloth')) if not hasattr(self, '_xml_cloth'): self._xml_cloth = None if not hasattr(self, '_xml_root_cloth'): self._xml_root_cloth = None if 'xml_cloth' in cls_dict: self.set_xml_cloth(cls_dict.pop('xml_cloth')) if 'xml_root_cloth' in cls_dict: self.set_xml_cloth(cls_dict.pop('xml_root_cloth')) if 'method_config_do' in cls_dict and \ cls_dict['method_config_do'] is not None: cls_dict['method_config_do'] = \ staticmethod(cls_dict['method_config_do']) super(AttributesMeta, self).__init__(cls_name, cls_bases, cls_dict) def get_nullable(self): return (self._nullable if self._nullable is not None else self.NULLABLE_DEFAULT) def set_nullable(self, what): self._nullable = what nullable = property(get_nullable, set_nullable) def get_nillable(self): return self.nullable def set_nillable(self, what): self.nullable = what nillable = property(get_nillable, set_nillable) def get_default_factory(self): return self._default_factory def set_default_factory(self, what): self._default_factory = staticmethod(what) default_factory = property(get_default_factory, set_default_factory) def get_html_cloth(self): return self._html_cloth def set_html_cloth(self, what): from spyne.protocol.cloth.to_cloth import ClothParserMixin cm = ClothParserMixin.from_html_cloth(what) if cm._root_cloth is not None: self._html_root_cloth = cm._root_cloth elif cm._cloth is not None: self._html_cloth = cm._cloth else: raise Exception("%r is not a suitable cloth", what) html_cloth = property(get_html_cloth, set_html_cloth) def get_html_root_cloth(self): return self._html_root_cloth html_root_cloth = property(get_html_root_cloth) def get_xml_cloth(self): return self._xml_cloth def set_xml_cloth(self, what): from spyne.protocol.cloth.to_cloth import ClothParserMixin cm = ClothParserMixin.from_xml_cloth(what) if cm._root_cloth is not None: self._xml_root_cloth = cm._root_cloth elif cm._cloth is not None: self._xml_cloth = cm._cloth else: raise Exception("%r is not a suitable cloth", what) xml_cloth = property(get_xml_cloth, set_xml_cloth) def get_xml_root_cloth(self): return self._xml_root_cloth xml_root_cloth = property(get_xml_root_cloth) def get_method_config_do(self): return self._method_config_do def set_method_config_do(self, what): if what is None: self._method_config_do = None else: self._method_config_do = staticmethod(what) method_config_do = property(get_method_config_do, set_method_config_do) class ModelBaseMeta(type(object)): def __getitem__(self, item): return self.customize(**item) def customize(self, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" cls_name, cls_bases, cls_dict = self._s_customize(**kwargs) return type(cls_name, cls_bases, cls_dict) @six.add_metaclass(ModelBaseMeta) class ModelBase(object): """The base class for type markers. It defines the model interface for the interface generators to use and also manages class customizations that are mainly used for defining constraints on input values. """ __orig__ = None """This holds the original class the class .customize()d from. Ie if this is None, the class is not a customize()d one.""" __extends__ = None """This holds the original class the class inherited or .customize()d from. This is different from __orig__ because it's only set when ``cls.is_default(cls) == False``""" __namespace__ = None """The public namespace of this class. Use ``get_namespace()`` instead of accessing it directly.""" __type_name__ = None """The public type name of the class. Use ``get_type_name()`` instead of accessing it directly.""" Value = type(None) """The value of this type is an instance of this class""" # These are not the xml schema defaults. The xml schema defaults are # considered in XmlSchema's add() method. the defaults here are to reflect # what people seem to want most. # # Please note that min_occurs and max_occurs must be validated in the # ComplexModelBase deserializer. @six.add_metaclass(AttributesMeta) class Attributes(object): """The class that holds the constraints for the given type.""" _wrapper = False # when skip_wrappers=True is passed to a protocol, these objects # are skipped. just for internal use. _explicit_type_name = False # set to true when type_name is passed to customize() call. out_type = None """Override serialization type. Usually, this designates the return type of the callable in the `sanitizer` attribute. If this is a two-way type, it may be a good idea to also use the `parser` attribute to perform reverse conversion.""" default = None """The default value if the input is None. Please note that this default is UNCONDITIONALLY applied in class initializer. It's recommended to at least make an effort to use this only in customized classes and not in original models. """ default_factory = None """The callable that produces a default value if the value is None. The warnings in ``default`` apply here as well.""" db_default = None """The default value used only when persisting the value if it is ``None``. Only works for primitives. Unlike ``default`` this can also be set to a callable that takes no arguments according to SQLAlchemy docs.""" nillable = None """Set this to false to reject null values. Synonyms with ``nullable``. True by default. The default value can be changed by setting ``AttributesMeta.NULLABLE_DEFAULT``.""" min_occurs = 0 """Set this to 1 to make this object mandatory. Can be set to any positive integer. Note that an object can still be null or empty, even if it's there.""" max_occurs = 1 """Can be set to any strictly positive integer. Values greater than 1 will imply an iterable of objects as native python type. Can be set to ``decimal.Decimal("inf")`` for arbitrary number of arguments.""" schema_tag = spyne.const.xml.XSD('element') """The tag used to add a primitives as child to a complex type in the xml schema.""" translations = None """A dict that contains locale codes as keys and translations of field names to that language as values. """ sub_ns = None """An Xml-specific attribute that specifies which namespace should be used for field names in classes. """ sub_name = None """This specifies which string should be used as field name when this type is seriazed under a ComplexModel. """ wsdl_part_name = None """This specifies which string should be used as wsdl message part name when this type is serialized under a ComplexModel ie."parameters". """ sqla_column_args = None """A dict that will be passed to SQLAlchemy's ``Column`` constructor as ``**kwargs``. """ exc_mapper = False """If true, this field will be excluded from the table mapper of the parent class. """ exc_table = False """DEPRECATED !!! Use ``exc_db`` instead.""" exc_db = False """If ``True``, this field will not be persisted to the database. This attribute only makes sense in a subfield of a ``ComplexModel`` subclass. """ exc_interface = False """If `True`, this field will be excluded from the interface document.""" exc = False """If `True`, this field will be excluded from all serialization or deserialization operations. See `prot_attrs` to make this only apply to a specific protocol class or instance.""" logged = True """If `False`, this object will be ignored in ``log_repr``, mostly used for logging purposes. * Primitives can have logger=``'...'`` which will always log the value as ``(...)``. * ``AnyDict`` can have one of ``('keys', 'keys-full', 'values', 'values-full, 'full')`` as logger value where for ``'keys'`` and ``'values'`` the output of ``keys()`` and ``values()`` will be logged up to MAX_DICT_ELEMENT_NUM number of elements and for ``'full'`` variants, all of the contents of the dict will be logged will be logged * ``Array`` can also have ``logger='full'`` where all of the value will be logged where as for simple ``logger=True`` only MAX_ARRAY_ELEMENT_NUM elements will be logged. * For ``ComplexModel`` subclasses sent as first value to log_repr, ``logger=False`` means a string of form ``ClassName(...)`` will be logged. """ sanitizer = None """A callable that takes the associated native type and returns the parsed value. Only called during serialization.""" parser = None """A callable that takes the associated native type and returns the parsed value. Only called during deserialization.""" unique = None """If True, this object will be set as unique in the database schema with default indexing options. If the value is a string, it will be used as the indexing method to create the unique index. See sqlalchemy documentation on how to create multi-column unique constraints. """ db_type = None """When not None, it overrides Spyne's own mapping from Spyne types to SQLAlchemy types. It's a standard SQLAlchemy type marker, e.g. ``sqlalchemy.Integer``. """ table_name = None """Database table name.""" xml_choice_group = None """When not None, shares the same tag with fields with the same xml_choice_group value. """ index = None """Can be ``True``, a string, or a tuple of two strings. * If True, this object will be set as indexed in the database schema with default options. * If the value is a string, the value will denote the indexing method used by the database. Should be one of: ('btree', 'gin', 'gist', 'hash', 'spgist') See: http://www.postgresql.org/docs/9.2/static/indexes-types.html * If the value is a tuple of two strings, the first value will denote the index name and the second value will denote the indexing method as above. """ read_only = False """If True, the attribute won't be initialized from outside values. Set this to ``True`` for e.g. read-only properties.""" prot_attrs = None """Customize child attributes for protocols. It's a dict of dicts. The key is either a ProtocolBase subclass or a ProtocolBase instance. Instances override classes.""" pa = None """Alias for prot_attrs.""" empty_is_none = False """When the incoming object is empty (e.g. '' for strings) treat it as None. No effect (yet) for outgoing values.""" order = None """An integer that's passed to ``_type_info.insert()`` as first argument when not None. ``.append()`` is used otherwise.""" validate_on_assignment = False """Perform validation on assignment (i.e. all the time) instead of on just serialization""" polymap = {} """A dict of classes that override polymorphic substitions for classes given as keys to classes given as values.""" class Annotations(object): """The class that holds the annotations for the given type.""" __use_parent_doc__ = False """If equal to True and doc is empty, Annotations will use __doc__ from parent. Set it to False to avoid this mechanism. This is a convenience option""" doc = "" """The public documentation for the given type.""" appinfo = None """Any object that carries app-specific info.""" class Empty(object): pass _force_own_namespace = None @classmethod def ancestors(cls): """Returns a list of parent classes in child-to-parent order.""" retval = [] extends = cls.__extends__ while extends is not None: retval.append(extends) extends = extends.__extends__ return retval @staticmethod def is_default(cls): return True @classmethod def get_namespace_prefix(cls, interface): """Returns the namespace prefix for the given interface. The get_namespace_prefix of the interface class generates a prefix if none is defined. """ ns = cls.get_namespace() retval = interface.get_namespace_prefix(ns) return retval @classmethod def get_namespace(cls): """Returns the namespace of the class. Defaults to the python module name.""" return cls.__namespace__ @classmethod def _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns cls.__type_name__ = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) extends = cls.__extends__ while extends is not None and extends.__type_name__ is ModelBase.Empty: cls.__extends__._fill_empty_type_name(cls.get_namespace(), cls.get_type_name(), k + const.PARENT_SUFFIX) extends = extends.__extends__ # TODO: rename to "resolve_identifier" @staticmethod def resolve_namespace(cls, default_ns, tags=None): """This call finalizes the namespace assignment. The default namespace is not available until the application calls populate_interface method of the interface generator. """ if tags is None: tags = set() elif cls in tags: return False tags.add(cls) if cls.__namespace__ is spyne.const.xml.DEFAULT_NS: cls.__namespace__ = default_ns if (cls.__namespace__ in spyne.const.xml.PREFMAP and not cls.is_default(cls)): cls.__namespace__ = default_ns if cls.__namespace__ is None: ret = [] for f in cls.__module__.split('.'): if f.startswith('_'): break else: ret.append(f) cls.__namespace__ = '.'.join(ret) if cls.__namespace__ is None or len(cls.__namespace__) == 0: cls.__namespace__ = default_ns if cls.__namespace__ is None or len(cls.__namespace__) == 0: raise ValueError("You need to explicitly set %r.__namespace__" % cls) # print(" resolve ns for %r to %r" % (cls, cls.__namespace__)) if getattr(cls, '__extends__', None) != None: cls.__extends__.resolve_namespace(cls.__extends__, default_ns, tags) return True @classmethod def get_type_name(cls): """Returns the class name unless the __type_name__ attribute is defined. """ retval = cls.__type_name__ if retval is None: retval = cls.__name__ return retval # FIXME: Rename this to get_type_name_with_ns_pref @classmethod def get_type_name_ns(cls, interface): """Returns the type name with a namespace prefix, separated by a column. """ if cls.get_namespace() != None: return "%s:%s" % (cls.get_namespace_prefix(interface), cls.get_type_name()) @classmethod def get_element_name(cls): return cls.Attributes.sub_name or cls.get_type_name() @classmethod def get_wsdl_part_name(cls): return cls.Attributes.wsdl_part_name or cls.get_element_name() @classmethod def get_element_name_ns(cls, interface): ns = cls.Attributes.sub_ns or cls.get_namespace() if ns is DEFAULT_NS: ns = interface.get_tns() if ns is not None: pref = interface.get_namespace_prefix(ns) return "%s:%s" % (pref, cls.get_element_name()) @classmethod def to_bytes(cls, value): """ Returns str(value). This should be overridden if this is not enough. """ return six.binary_type(value) @classmethod def to_unicode(cls, value): """ Returns unicode(value). This should be overridden if this is not enough. """ return six.text_type(value) @classmethod def get_documentation(cls): if cls.Annotations.doc: return cls.Annotations.doc elif cls.Annotations.__use_parent_doc__: return cls.__doc__ else: return '' @classmethod def _s_customize(cls, **kwargs): """Sanitizes customization parameters of the class it belongs to. Doesn't perform any actual customization. """ def _log_debug(s, *args): logger.debug("\t%s: %s" % (cls.get_type_name(), s), *args) cls_dict = odict({'__module__': cls.__module__, '__doc__': cls.__doc__}) if getattr(cls, '__orig__', None) is None: cls_dict['__orig__'] = cls else: cls_dict['__orig__'] = cls.__orig__ class Attributes(cls.Attributes): _explicit_type_name = False if cls.Attributes.translations is None: Attributes.translations = {} if cls.Attributes.sqla_column_args is None: Attributes.sqla_column_args = (), {} else: Attributes.sqla_column_args = deepcopy( cls.Attributes.sqla_column_args) cls_dict['Attributes'] = Attributes # properties get reset every time a new class is defined. So we need # to reinitialize them explicitly. for k in ('nillable', '_xml_cloth', '_xml_root_cloth', '_html_cloth', '_html_root_cloth'): v = getattr(cls.Attributes, k) if v is not None: setattr(Attributes, k, v) class Annotations(cls.Annotations): pass cls_dict['Annotations'] = Annotations # get protocol attrs prot = kwargs.get('protocol', None) if prot is None: prot = kwargs.get('prot', None) if prot is None: prot = kwargs.get('p', None) if prot is not None and len(prot.type_attrs) > 0: # if there is a class customization from protocol, do it type_attrs = prot.type_attrs.copy() type_attrs.update(kwargs) _log_debug("kwargs %r => %r from prot typeattr %r", kwargs, type_attrs, prot.type_attrs) kwargs = type_attrs # the ones that wrap values in staticmethod() should be added to # AttributesMeta initializer for k, v in kwargs.items(): if k.startswith('_'): _log_debug("ignoring '%s' because of leading underscore", k) continue if k in ('protocol', 'prot', 'p'): Attributes.prot = v _log_debug("setting prot=%r", v) elif k in ('voa', 'validate_on_assignment'): Attributes.validate_on_assignment = v _log_debug("setting voa=%r", v) elif k in ('parser', 'in_cast'): setattr(Attributes, 'parser', staticmethod(v)) _log_debug("setting %s=%r", k, v) elif k in ('sanitize', 'sanitizer', 'out_cast'): setattr(Attributes, 'sanitizer', staticmethod(v)) _log_debug("setting %s=%r as sanitizer", k, v) elif k == 'logged': setattr(Attributes, 'logged', staticmethod(v)) _log_debug("setting %s=%r as log sanitizer", k, v) elif k in ("doc", "appinfo"): setattr(Annotations, k, v) _log_debug("setting Annotations.%s=%r", k, v) elif k in ('primary_key', 'pk'): setattr(Attributes, 'primary_key', v) Attributes.sqla_column_args[-1]['primary_key'] = v _log_debug("setting primary_key=%r", v) elif k in ('protocol_attrs', 'prot_attrs', 'pa'): setattr(Attributes, 'prot_attrs', _decode_pa_dict(v)) _log_debug("setting prot_attrs=%r", v) elif k in ('foreign_key', 'fk'): from sqlalchemy.schema import ForeignKey t, d = Attributes.sqla_column_args fkt = (ForeignKey(v),) new_v = (t + fkt, d) Attributes.sqla_column_args = new_v _log_debug("setting sqla_column_args=%r", new_v) elif k in ('autoincrement', 'onupdate', 'server_default'): Attributes.sqla_column_args[-1][k] = v _log_debug("adding %s=%r to Attributes.sqla_column_args", k, v) elif k == 'values_dict': assert not 'values' in v, "`values` and `values_dict` can't be" \ "specified at the same time" if not isinstance(v, dict): # our odict has one nasty implicit behaviour: setitem on # int keys is treated as array indexes, not dict keys. so # dicts with int indexes can't work with odict. so we use # the one from stdlib v = OrderedDict(v) Attributes.values = list(v.keys()) Attributes.values_dict = v _log_debug("setting values=%r, values_dict=%r", Attributes.values, Attributes.values_dict) elif k == 'exc_table': Attributes.exc_table = v Attributes.exc_db = v _log_debug("setting exc_table=%r, exc_db=%r", v, v) elif k == 'max_occurs' and v in ('unbounded', 'inf', float('inf')): new_v = decimal.Decimal('inf') setattr(Attributes, k, new_v) _log_debug("setting max_occurs=%r", new_v) elif k == 'type_name': Attributes._explicit_type_name = True _log_debug("setting _explicit_type_name=True because " "we have 'type_name'") else: setattr(Attributes, k, v) _log_debug("setting %s=%r", k, v) return (cls.__name__, (cls,), cls_dict) @staticmethod def validate_string(cls, value): """Override this method to do your own input validation on the input string. This is called before converting the incoming string to the native python value.""" return (cls.Attributes.nillable or value is not None) @staticmethod def validate_native(cls, value): """Override this method to do your own input validation on the native value. This is called after converting the incoming string to the native python value.""" return (cls.Attributes.nullable or value is not None) class Null(ModelBase): pass class SimpleModelAttributesMeta(AttributesMeta): def __init__(self, cls_name, cls_bases, cls_dict): super(SimpleModelAttributesMeta, self).__init__(cls_name, cls_bases, cls_dict) if getattr(self, '_pattern', None) is None: self._pattern = None def get_pattern(self): return self._pattern def set_pattern(self, pattern): self._pattern = pattern if pattern is not None: self._pattern_re = re.compile(pattern) pattern = property(get_pattern, set_pattern) def get_unicode_pattern(self): return self._pattern def set_unicode_pattern(self, pattern): self._pattern = pattern if pattern is not None: self._pattern_re = re.compile(pattern, re.UNICODE) unicode_pattern = property(get_unicode_pattern, set_unicode_pattern) upattern = property(get_unicode_pattern, set_unicode_pattern) class SimpleModel(ModelBase): """The base class for primitives.""" __namespace__ = "http://www.w3.org/2001/XMLSchema" @six.add_metaclass(SimpleModelAttributesMeta) class Attributes(ModelBase.Attributes): """The class that holds the constraints for the given type.""" values = set() """The set of possible values for this type.""" # some hacks are done in _s_customize to make `values_dict` # behave like `values` values_dict = dict() """The dict of possible values for this type. Dict keys are values and dict values are either a single string or a translation dict.""" _pattern_re = None def __new__(cls, **kwargs): """Overriden so that any attempt to instantiate a primitive will return a customized class instead of an instance. See spyne.model.base.ModelBase for more information. """ return cls.customize(**kwargs) @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" cls_name, cls_bases, cls_dict = cls._s_customize(**kwargs) retval = type(cls_name, cls_bases, cls_dict) if not retval.is_default(retval): retval.__extends__ = cls retval.__type_name__ = kwargs.get("type_name", ModelBase.Empty) if 'type_name' in kwargs: logger.debug("Type name for %r was overridden as '%s'", retval, retval.__type_name__) retval.resolve_namespace(retval, kwargs.get('__namespace__')) return retval @staticmethod def is_default(cls): return (cls.Attributes.values == SimpleModel.Attributes.values) @staticmethod def validate_native(cls, value): return (ModelBase.validate_native(cls, value) and ( cls.Attributes.values is None or len(cls.Attributes.values) == 0 or ( (value is None and cls.Attributes.nillable) or (value is not None and value in cls.Attributes.values) ) ) ) class PushBase(object): def __init__(self, callback=None, errback=None): self.orig_thread = threading.current_thread() self._cb = callback self._eb = errback self.length = 0 self.ctx = None self.app = None self.gen = None self._cb_finish = None self._eb_finish = None self.interim = False def _init(self, ctx, gen, _cb_finish, _eb_finish, interim): self.length = 0 self.ctx = ctx self.app = ctx.app self.gen = gen self._cb_finish = _cb_finish self._eb_finish = _eb_finish self.interim = interim def init(self, ctx, gen, _cb_finish, _eb_finish, interim): self._init(ctx, gen, _cb_finish, _eb_finish, interim) if self._cb is not None: return self._cb(self) def __len__(self): return self.length def append(self, inst): self.gen.send(inst) self.length += 1 def extend(self, insts): for inst in insts: self.gen.send(inst) self.length += 1 def close(self): try: self.gen.throw(Break()) except (Break, StopIteration, GeneratorExit): pass self._cb_finish() class xml: """Compound option object for xml serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. :param root_tag: Root tag of the xml element that contains the field values. :param no_ns: When true, the xml document is stripped from namespace information. This is generally a stupid thing to do. Use with caution. """ def __init__(self, root_tag=None, no_ns=False, pretty_print=False): self.root_tag = root_tag self.no_ns = no_ns self.pretty_print = pretty_print class table: """Compound option object for for storing the class instance as in row in a table in a relational database. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. :param multi: When False, configures a one-to-many relationship where the child table has a foreign key to the parent. When not ``False``, configures a many-to-many relationship by creating an intermediate relation table that has foreign keys to both parent and child classes and generates a table name automatically. When ``True``, the table name is generated automatically. Otherwise, it should be a string, as the value is used as the name of the intermediate table. :param left: Name of the left join column. :param right: Name of the right join column. :param backref: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.backref :param cascade: https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.cascade :param lazy: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.lazy :param back_populates: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.back_populates """ def __init__(self, multi=False, left=None, right=None, backref=None, id_backref=None, cascade=False, lazy='select', back_populates=None, fk_left_deferrable=None, fk_left_initially=None, fk_right_deferrable=None, fk_right_initially=None, fk_left_ondelete=None, fk_left_onupdate=None, fk_right_ondelete=None, fk_right_onupdate=None, explicit_join=False, order_by=False, single_parent=None): self.multi = multi self.left = left self.right = right self.backref = backref self.id_backref = id_backref self.cascade = cascade self.lazy = lazy self.back_populates = back_populates self.fk_left_deferrable = fk_left_deferrable self.fk_left_initially = fk_left_initially self.fk_right_deferrable = fk_right_deferrable self.fk_right_initially = fk_right_initially self.fk_left_ondelete = fk_left_ondelete self.fk_left_onupdate = fk_left_onupdate self.fk_right_ondelete = fk_right_ondelete self.fk_right_onupdate = fk_right_onupdate self.explicit_join = explicit_join self.order_by = order_by self.single_parent = single_parent class json: """Compound option object for json serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. Make sure you don't mix this with the json package when importing. """ def __init__(self, ignore_wrappers=True, complex_as=dict): if ignore_wrappers != True: raise NotImplementedError("ignore_wrappers != True") self.ignore_wrappers = ignore_wrappers self.complex_as = complex_as class jsonb: """Compound option object for jsonb serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. """ def __init__(self, ignore_wrappers=True, complex_as=dict): if ignore_wrappers != True: raise NotImplementedError("ignore_wrappers != True") self.ignore_wrappers = ignore_wrappers self.complex_as = complex_as class msgpack: """Compound option object for msgpack serialization. It's meant to be passed to :func:`ComplexModelBase.Attributes.store_as`. Make sure you don't mix this with the msgpack package when importing. """ def __init__(self): pass PSSM_VALUES = {'json': json, 'jsonb': jsonb, 'xml': xml, 'msgpack': msgpack, 'table': table} def apply_pssm(val): if val is not None: val_c = PSSM_VALUES.get(val, None) if val_c is None: assert isinstance(val, tuple(PSSM_VALUES.values())), \ "'store_as' should be one of: %r or an instance of %r not %r" \ % (tuple(PSSM_VALUES.keys()), tuple(PSSM_VALUES.values()), val) return val return val_c() spyne-spyne-2.14.0/spyne/model/addtl.py000066400000000000000000000057051417664205300200070ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import re from spyne import M, Boolean, DateTime, Date, Time, ComplexModel, \ ValidationError from spyne.protocol import InProtocolBase class SegmentBase(object): @classmethod def from_string(cls, s): match = cls._SEGMENT_RE.match(s) if match is None: raise ValidationError(s) start_incl, start_str, end_str, end_incl = match.groups() print() print(start_incl, start_str, end_str, end_incl) start_incl = (start_incl == '[') start = InProtocolBase().from_unicode( cls._type_info['start'], start_str) end = InProtocolBase().from_unicode(cls._type_info['start'], end_str) end_incl = (end_incl == ']') print(start_incl, start, end, end_incl) return cls(start_inclusive=start_incl, start=start, end=end, end_inclusive=end_incl) def to_string(self): return '[%s,%s]' % (self.start.isoformat(), self.end.isoformat()) class DateTimeSegment(ComplexModel, SegmentBase): _SEGMENT_RE = re.compile( u"([\\[\\]])" u"([0-9:\\.T-]+)" u"," u"([0-9:\\.T-]+)" u"([\\[\\]])", re.DEBUG | re.UNICODE) _type_info = [ ('start_inclusive', M(Boolean(default=True))), ('start', M(DateTime)), ('end', M(DateTime)), ('end_inclusive', M(Boolean(default=True))), ] class DateSegment(ComplexModel, SegmentBase): _SEGMENT_RE = re.compile( u"([\\[\\]])" u"([0-9-]+)" u"," u"([0-9-]+)" u"([\\[\\]])", re.DEBUG | re.UNICODE) _type_info = [ ('start_inclusive', M(Boolean(default=True))), ('start', M(Date)), ('end', M(Date)), ('end_inclusive', M(Boolean(default=True))), ] class TimeSegment(ComplexModel, SegmentBase): _SEGMENT_RE = re.compile( u"([\\[\\]])" u"([0-9:\\.]+)" u"," u"([0-9:\\.]+)" u"([\\[\\]])", re.DEBUG | re.UNICODE) _type_info = [ ('start_inclusive', M(Boolean(default=True))), ('start', M(Time)), ('end', M(Time)), ('end_inclusive', M(Boolean(default=True))), ] spyne-spyne-2.14.0/spyne/model/binary.py000066400000000000000000000312011417664205300201710ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.model.binary`` package contains binary type markers.""" import logging logger = logging.getLogger(__name__) import os import base64 import tempfile import errno from mmap import mmap, ACCESS_READ, error as MmapError from base64 import b64encode from base64 import b64decode from base64 import urlsafe_b64encode from base64 import urlsafe_b64decode from binascii import hexlify from binascii import unhexlify from os.path import abspath, isdir, isfile, basename from spyne.error import ValidationError from spyne.util import _bytes_join from spyne.model import ComplexModel, Unicode from spyne.model import SimpleModel from spyne.util import six from spyne.util.six import BytesIO, StringIO class BINARY_ENCODING_HEX: pass class BINARY_ENCODING_BASE64: pass class BINARY_ENCODING_USE_DEFAULT: pass class BINARY_ENCODING_URLSAFE_BASE64: pass class ByteArray(SimpleModel): """Canonical container for arbitrary data. Every protocol has a different way of encapsulating this type. E.g. xml-based protocols encode this as base64, while HttpRpc just hands it over. Its native python format is a sequence of ``str`` objects for Python 2.x and a sequence of ``bytes`` objects for Python 3.x. """ __type_name__ = 'base64Binary' __namespace__ = "http://www.w3.org/2001/XMLSchema" class Attributes(SimpleModel.Attributes): encoding = BINARY_ENCODING_USE_DEFAULT """The binary encoding to use when the protocol does not enforce an encoding for binary data. One of (None, 'base64', 'hex') """ def __new__(cls, **kwargs): tn = None if 'encoding' in kwargs: v = kwargs['encoding'] if v is None: kwargs['encoding'] = BINARY_ENCODING_USE_DEFAULT elif v in ('base64', 'base64Binary', BINARY_ENCODING_BASE64): # This string is defined in the Xml Schema Standard tn = 'base64Binary' kwargs['encoding'] = BINARY_ENCODING_BASE64 elif v in ('urlsafe_base64', BINARY_ENCODING_URLSAFE_BASE64): # the Xml Schema Standard does not define urlsafe base64 # FIXME: produce a regexp that validates urlsafe base64 strings tn = 'string' kwargs['encoding'] = BINARY_ENCODING_URLSAFE_BASE64 elif v in ('hex', 'hexBinary', BINARY_ENCODING_HEX): # This string is defined in the Xml Schema Standard tn = 'hexBinary' kwargs['encoding'] = BINARY_ENCODING_HEX else: raise ValueError("'encoding' must be one of: %r" % \ (tuple(ByteArray._encoding.handlers.values()),)) retval = cls.customize(**kwargs) if tn is not None: retval.__type_name__ = tn return retval @staticmethod def is_default(cls): return True @classmethod def to_base64(cls, value): if isinstance(value, (list, tuple)) and isinstance(value[0], mmap): # TODO: be smarter about this return b64encode(value[0]) if isinstance(value, (six.binary_type, memoryview, mmap)): return b64encode(value) return b64encode(b''.join(value)) @classmethod def from_base64(cls, value): joiner = type(value)() try: return (b64decode(joiner.join(value)),) except TypeError: raise ValidationError(value) @classmethod def to_urlsafe_base64(cls, value): if isinstance(value, (list, tuple)): return urlsafe_b64encode(_bytes_join(value)) else: return urlsafe_b64encode(value) @classmethod def from_urlsafe_base64(cls, value): #FIXME: Find out why we need to do this. if isinstance(value, six.text_type): value = value.encode('utf8') try: if isinstance(value, (list, tuple)): return (urlsafe_b64decode(_bytes_join(value)),) else: return (urlsafe_b64decode(value),) except TypeError as e: logger.exception(e) if len(value) < 100: raise ValidationError(value) else: raise ValidationError(value[:100] + b"(...)") @classmethod def to_hex(cls, value): return hexlify(_bytes_join(value)) @classmethod def from_hex(cls, value): return (unhexlify(_bytes_join(value)),) def _default_binary_encoding(b): if isinstance(b, (six.binary_type, memoryview)): return b if isinstance(b, tuple) and len(b) > 0 and isinstance(b[0], mmap): return b[0] if isinstance(b, six.text_type): raise ValueError(b) return b''.join(b) binary_encoding_handlers = { None: _default_binary_encoding, BINARY_ENCODING_HEX: ByteArray.to_hex, BINARY_ENCODING_BASE64: ByteArray.to_base64, BINARY_ENCODING_URLSAFE_BASE64: ByteArray.to_urlsafe_base64, } binary_decoding_handlers = { None: lambda x: (x,), BINARY_ENCODING_HEX: ByteArray.from_hex, BINARY_ENCODING_BASE64: ByteArray.from_base64, BINARY_ENCODING_URLSAFE_BASE64: ByteArray.from_urlsafe_base64, } class HybridFileStore(object): def __init__(self, store_path, db_format='json', type=None): """Marker to be passed to File's store_as to denote a hybrid Sql/Filesystem storage scheme. :param store_path: The path where the file contents are stored. This is converted to an absolute path if it's not already one. :param db_format: The format (and the relevant column type) used to store file metadata. Currently only 'json' is implemented. """ self.store = abspath(store_path) self.db_format = db_format self.type = type if not isdir(self.store): os.makedirs(self.store) assert isdir(self.store) _BINARY = type('FileTypeBinary', (object,), {}) _TEXT = type('FileTypeText', (object,), {}) class SanitizationError(ValidationError): def __init__(self, obj): super(SanitizationError, self).__init__( obj, "%r was not sanitized before use") class _FileValue(ComplexModel): """The class for values marked as ``File``. :param name: Original name of the file :param type: The mime type of the file's contents. :param data: Optional sequence of ``str`` or ``bytes`` instances that contain the file's data. """ # ^ This is the public docstring. __type_name__ = "FileValue" _type_info = [ ('name', Unicode(encoding='utf8')), ('type', Unicode), ('data', ByteArray(logged='len')), ] def __init__(self, name=None, path=None, type='application/octet-stream', data=None, handle=None, move=False, _sanitize=True): self.name = name """The file basename, no directory information here.""" if self.name is not None and _sanitize: if not os.path.basename(self.name) == self.name: raise ValidationError(self.name, "File name %r should not contain any directory information") self.sanitized = _sanitize self.path = path """Relative path of the file.""" self.type = type """Mime type of the file""" self.data = data """The contents of the file. It's a sequence of str/bytes objects by contract. It can contain the contents of a the file as a single instance of `mmap.mmap()` object inside a tuple.""" self.handle = handle """The file handle.""" self.move = move """When True, Spyne can move the file (instead of copy) to its final location where it will be persisted. Defaults to `False`. See PGFile* objects to see how it's used.""" self.abspath = None """The absolute path of the file. It can be None even when the data is file-backed.""" if self.path is not None: self.abspath = abspath(self.path) def rollover(self): """This method normalizes the file object by making ``path``, ``name`` and ``handle`` properties consistent. It writes incoming data to the file object and points the ``data`` iterable to the contents of this file. """ if not self.sanitized: raise SanitizationError(self) if self.data is not None: if self.path is None: self.handle = tempfile.NamedTemporaryFile() self.abspath = self.path = self.handle.name self.name = basename(self.abspath) else: self.handle = open(self.path, 'wb') # FIXME: abspath could be None here, how do we make sure it's # the right value? # data is a ByteArray, so a sequence of str/bytes objects for d in self.data: self.handle.write(d) elif self.handle is not None: try: if isinstance(self.handle, (StringIO, BytesIO)): self.data = (self.handle.getvalue(),) else: # 0 = whole file self.data = (mmap(self.handle.fileno(), 0),) except MmapError as e: if e.errno == errno.EACCES: self.data = ( mmap(self.handle.fileno(), 0, access=ACCESS_READ), ) else: raise elif self.path is not None: if not isfile(self.path): logger.error("File path in %r not found", self) self.handle = open(self.path, 'rb') self.data = (mmap(self.handle.fileno(), 0, access=ACCESS_READ),) self.abspath = abspath(self.path) self.name = self.path = basename(self.path) else: raise ValueError("Invalid file object passed in. All of " ".data, .handle and .path are None.") class File(SimpleModel): """A compact way of dealing with incoming files for protocols with a standard way of encoding file metadata along with binary data. (E.g. Http) """ __type_name__ = 'base64Binary' __namespace__ = "http://www.w3.org/2001/XMLSchema" BINARY = _BINARY TEXT = _TEXT Value = _FileValue class Attributes(SimpleModel.Attributes): encoding = BINARY_ENCODING_USE_DEFAULT """The binary encoding to use when the protocol does not enforce an encoding for binary data. One of (None, 'base64', 'hex') """ type = _FileValue """The native type used to serialize the information in the file object. """ mode = _BINARY """Set this to mode=File.TEXT if you're sure you're handling unicode data. This lets serializers like HtmlCloth avoid base64 encoding. Do note that you still need to set encoding attribute explicitly to None!.. One of (File.BINARY, File.TEXT) """ @classmethod def to_base64(cls, value): if value is None: return assert value.path, "You need to write data to persistent storage first " \ "if you want to read it back." f = open(value.path, 'rb') # base64 encodes every 3 bytes to 4 base64 characters data = f.read(0x4001) # so this needs to be a multiple of 3 while len(data) > 0: yield base64.b64encode(data) data = f.read(0x4001) f.close() @classmethod def from_base64(cls, value): if value is None: return None return File.Value(data=[base64.b64decode(value)]) def __repr__(self): return "File(name=%r, path=%r, type=%r, data=%r)" % \ (self.name, self.path, self.type, self.data) @classmethod def store_as(cls, what): return cls.customize(store_as=what) spyne-spyne-2.14.0/spyne/model/complex.py000066400000000000000000001567151417664205300203760ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.model.complex`` module contains :class:`spyne.model.complex.ComplexBase` class and its helper objects and subclasses. These are mainly container classes for other simple or complex objects -- they don't carry any data by themselves. """ from __future__ import print_function import logging logger = logging.getLogger(__name__) import decimal import traceback from copy import copy from weakref import WeakKeyDictionary from collections import deque, OrderedDict from inspect import isclass from itertools import chain from spyne import const from spyne.const.xml import PREFMAP from spyne.model import Point, Unicode, PushBase, ModelBase from spyne.model._base import PSSM_VALUES, apply_pssm from spyne.model.primitive import NATIVE_MAP from spyne.model.primitive._base import AnyXml from spyne.util import six, memoize, memoize_id, sanitize_args, \ memoize_ignore_none from spyne.util.color import YEL from spyne.util.meta import Prepareable from spyne.util.odict import odict from spyne.util.six import add_metaclass, with_metaclass, string_types # FIXME: for backwards compatibility, to be removed in Spyne 3 # noinspection PyUnresolvedReferences from spyne.model import json, jsonb, xml, msgpack, table def _get_flat_type_info(cls, retval): assert isinstance(retval, TypeInfo) parent = getattr(cls, '__extends__', None) if not (parent is None): _get_flat_type_info(parent, retval) retval.update(cls._type_info) retval.alt.update(cls._type_info_alt) # FIXME: move to cls._type_info.alt retval.attrs.update({k: v for (k, v) in cls._type_info.items() if issubclass(v, XmlAttribute)}) return retval class TypeInfo(odict): def __init__(self, *args, **kwargs): super(TypeInfo, self).__init__(*args, **kwargs) self.attributes = {} self.alt = OrderedDict() self.attrs = OrderedDict() def __setitem__(self, key, val): assert isinstance(key, string_types) super(TypeInfo, self).__setitem__(key, val) class _SimpleTypeInfoElement(object): __slots__ = ['path', 'parent', 'type', 'is_array', 'can_be_empty'] def __init__(self, path, parent, type_, is_array, can_be_empty): self.path = path self.parent = parent self.type = type_ self.is_array = is_array self.can_be_empty = can_be_empty def __repr__(self): return "SimpleTypeInfoElement(path=%r, parent=%r, type=%r, is_array=%r)" \ % (self.path, self.parent, self.type, self.is_array) class XmlModifier(ModelBase): def __new__(cls, type, ns=None): retval = cls.customize() retval.type = type retval.Attributes = type.Attributes retval._ns = ns if type.__type_name__ is ModelBase.Empty: retval.__type_name__ = ModelBase.Empty return retval @staticmethod def resolve_namespace(cls, default_ns, tags=None): cls.type.resolve_namespace(cls.type, default_ns, tags) cls.__namespace__ = cls._ns if cls.__namespace__ is None: cls.__namespace__ = cls.type.get_namespace() if cls.__namespace__ in PREFMAP: cls.__namespace__ = default_ns @classmethod def _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns tn = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) child_v = cls.type child_v.__type_name__ = tn cls._type_info = TypeInfo({tn: child_v}) cls.__type_name__ = '%s%s%s' % (const.ARRAY_PREFIX, tn, const.ARRAY_SUFFIX) extends = child_v.__extends__ while extends is not None and extends.get_type_name() is cls.Empty: extends._fill_empty_type_name(parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ class XmlData(XmlModifier): """Items which are marshalled as data of the parent element.""" @classmethod def marshall(cls, prot, name, value, parent_elt): if value is not None: if issubclass(cls.type, AnyXml): parent_elt.append(value) else: if len(parent_elt) == 0: parent_elt.text = prot.to_bytes(cls.type, value) else: parent_elt[-1].tail = prot.to_bytes(cls.type, value) @classmethod def get_type_name(cls): return cls.type.get_type_name() @classmethod def get_type_name_ns(cls, interface): return cls.type.get_type_name_ns(interface) @classmethod def get_namespace(cls): return cls.type.get_namespace() @classmethod def get_element_name(cls): return cls.type.get_element_name() @classmethod def get_element_name_ns(cls, interface): return cls.type.get_element_name_ns(interface) class XmlAttribute(XmlModifier): """Items which are marshalled as attributes of the parent element.""" def __new__(cls, type_, use=None, ns=None): retval = super(XmlAttribute, cls).__new__(cls, type_, ns) retval._use = use if retval.type.Attributes.min_occurs > 0 and retval._use is None: retval._use = 'required' return retval class XmlAttributeRef(XmlAttribute): """Reference to an Xml attribute.""" def __init__(self, ref, use=None): self._ref = ref self._use = use def describe(self, name, element, app): element.set('ref', self._ref) if self._use: element.set('use', self._use) class SelfReference(object): """Use this as a placeholder type in classes that contain themselves. See :func:`spyne.test.model.test_complex.TestComplexModel.test_self_reference`. """ customize_args = [] customize_kwargs = {} __orig__ = None def __init__(self): raise NotImplementedError() @classmethod def customize(cls, *args, **kwargs): args = list(chain(args, cls.customize_args)) kwargs = dict(chain(kwargs.items(), cls.customize_kwargs.items())) if cls.__orig__ is None: cls.__orig__ = cls return type("SelfReference", (cls,), { 'customize_args': args, 'customize_kwargs': kwargs, }) def _get_spyne_type(cls_name, k, v): try: v = NATIVE_MAP.get(v, v) except TypeError: return try: subc = issubclass(v, ModelBase) or issubclass(v, SelfReference) except: subc = False if subc: if issubclass(v, Array) and len(v._type_info) != 1: raise Exception("Invalid Array definition in %s.%s."% (cls_name, k)) elif issubclass(v, Point) and v.Attributes.dim is None: raise Exception("Please specify the number of dimensions") return v def _join_args(x, y): if x is None: return y if y is None: return x xa, xk = sanitize_args(x) ya, yk = sanitize_args(y) xk = dict(xk) xk.update(yk) return xa + ya, xk def _gen_attrs(cls_bases, cls_dict): attrs = cls_dict.get('Attributes', None) if attrs is None: for b in cls_bases: if hasattr(b, 'Attributes'): class Attributes(b.Attributes): pass attrs = cls_dict['Attributes'] = Attributes break else: raise Exception("No ModelBase subclass in bases? Huh?") return attrs def _get_type_info(cls, cls_name, cls_bases, cls_dict, attrs): base_type_info = TypeInfo() mixin = TypeInfo() extends = cls_dict.get('__extends__', None) # user did not specify explicit base class so let's try to derive it from # the actual class hierarchy if extends is None: # we don't want origs end up as base classes orig = cls_dict.get("__orig__", None) if orig is None: orig = getattr(cls, '__orig__', None) if orig is not None: bases = orig.__bases__ logger.debug("Got bases for %s from orig: %r", cls_name, bases) else: bases = cls_bases logger.debug("Got bases for %s from meta: %r", cls_name, bases) for b in bases: base_types = getattr(b, "_type_info", None) # we don't care about non-ComplexModel bases if base_types is None: continue # mixins are simple if getattr(b, '__mixin__', False) == True: logger.debug("Adding fields from mixin %r to '%s'", b, cls_name) mixin.update(b.get_flat_type_info(b)) if '__mixin__' not in cls_dict: cls_dict['__mixin__'] = False continue if not (extends in (None, b)): raise Exception("Spyne objects do not support multiple " "inheritance. Use mixins if you need to reuse " "fields from multiple classes.") if len(base_types) > 0 and issubclass(b, ModelBase): extends = cls_dict["__extends__"] = b assert extends.__orig__ is None, "You can't inherit from a " \ "customized class. You should first get your class " \ "hierarchy right, then start customizing classes." b.get_subclasses.memo.clear() logger.debug("Registering %r as base of '%s'", b, cls_name) if not ('_type_info' in cls_dict): cls_dict['_type_info'] = _type_info = TypeInfo() _type_info.update(base_type_info) class_fields = [] for k, v in cls_dict.items(): if k.startswith('_'): continue if isinstance(v, tuple) and len(v) == 1 and \ _get_spyne_type(cls_name, k, v[0]) is not None: logger.warning(YEL("There seems to be a stray comma in the" "definition of '%s.%s'.", cls_name, k)) v = _get_spyne_type(cls_name, k, v) if v is None: continue class_fields.append((k, v)) _type_info.update(class_fields) else: _type_info = cls_dict['_type_info'] if not isinstance(_type_info, TypeInfo): _type_info = cls_dict['_type_info'] = TypeInfo(_type_info) for k, v in reversed(mixin.items()): _type_info.insert(0, (k, v)) return _type_info class _MethodsDict(dict): def __init__(self, *args, **kwargs): super(_MethodsDict, self).__init__(*args, **kwargs) self._processed = False def _gen_methods(cls, cls_dict): methods = _MethodsDict() for k, v in cls_dict.items(): if not k.startswith('_') and hasattr(v, '_is_rpc'): logger.debug("Registering %s as member method for %r", k, cls) assert cls is not None # generate method descriptor from information in the decorator descriptor = v(_default_function_name=k, _self_ref_replacement=cls) # strip the decorator and put the original function in the class setattr(cls, k, descriptor.function) # modify the descriptor with user-supplied class if cls.Attributes.method_config_do is not None: descriptor = cls.Attributes.method_config_do(descriptor) methods[k] = descriptor return methods def _get_ordered_attributes(cls_name, cls_dict, attrs): if not isinstance(cls_dict, odict): # FIXME: Maybe add a warning here? return cls_dict SUPPORTED_ORDERS = ('random', 'declared') if (attrs.declare_order is not None and not attrs.declare_order in SUPPORTED_ORDERS): msg = "The declare_order attribute value %r is invalid in %s" raise Exception(msg % (attrs.declare_order, cls_name)) declare_order = attrs.declare_order or const.DEFAULT_DECLARE_ORDER if declare_order is None or declare_order == 'random': # support old behaviour cls_dict = dict(cls_dict) return cls_dict def _sanitize_sqlalchemy_parameters(cls_dict, attrs): table_name = cls_dict.get('__tablename__', None) if attrs.table_name is None: attrs.table_name = table_name _cls_table = cls_dict.get('__table__', None) if attrs.sqla_table is None: attrs.sqla_table = _cls_table metadata = cls_dict.get('__metadata__', None) if attrs.sqla_metadata is None: attrs.sqla_metadata = metadata margs = cls_dict.get('__mapper_args__', None) attrs.sqla_mapper_args = _join_args(attrs.sqla_mapper_args, margs) targs = cls_dict.get('__table_args__', None) attrs.sqla_table_args = _join_args(attrs.sqla_table_args, targs) def _sanitize_type_info(cls_name, _type_info, _type_info_alt): """Make sure _type_info contents are sane""" for k, v in _type_info.items(): if not isinstance(k, six.string_types): raise ValueError("Invalid class key", k) if not isclass(v): raise ValueError(v) if issubclass(v, SelfReference): continue elif not issubclass(v, ModelBase): v = _get_spyne_type(cls_name, k, v) if v is None: raise ValueError( (cls_name, k, v) ) _type_info[k] = v elif issubclass(v, Array) and len(v._type_info) != 1: raise Exception("Invalid Array definition in %s.%s." % (cls_name, k)) sub_ns = v.Attributes.sub_ns sub_name = v.Attributes.sub_name if sub_ns is None and sub_name is None: pass elif sub_ns is not None and sub_name is not None: key = "{%s}%s" % (sub_ns, sub_name) if key in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k elif sub_ns is None: key = sub_name if sub_ns in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k elif sub_name is None: key = "{%s}%s" % (sub_ns, k) if key in _type_info: raise Exception("%r is already defined: %r" % (key, _type_info[key])) _type_info_alt[key] = v, k D_EXC = dict(exc=True) def _process_child_attrs(cls, retval, kwargs): child_attrs = copy(kwargs.get('child_attrs', None)) child_attrs_all = kwargs.get('child_attrs_all', None) child_attrs_noexc = copy(kwargs.get('child_attrs_noexc', None)) # add exc=False to child_attrs_noexc if child_attrs_noexc is not None: # if there is _noexc, make sure that child_attrs_all is also used to # exclude exclude everything else first if child_attrs_all is None: child_attrs_all = D_EXC else: if 'exc' in child_attrs_all and child_attrs_all['exc'] != D_EXC: logger.warning("Overriding child_attrs_all['exc'] to True " "for %r", cls) child_attrs_all.update(D_EXC) # update child_attrs_noexc with exc=False for k, v in child_attrs_noexc.items(): if 'exc' in v: logger.warning("Overriding 'exc' for %s.%s from " "child_attrs_noexc with False", cls.get_type_name(), k) v['exc'] = False # update child_attrs with data from child_attrs_noexc if child_attrs is None: child_attrs = child_attrs_noexc else: # update with child_attrs_noexc with exc=False if child_attrs is None: child_attrs = dict() for k, v in child_attrs_noexc.items(): if k in child_attrs: logger.warning("Overriding child_attrs for %s.%s from " "child_attrs_noexc", cls.get_type_name(), k) child_attrs[k] = v if child_attrs_all is not None: ti = retval._type_info logger.debug("processing child_attrs_all for %r", cls) for k, v in ti.items(): logger.debug(" child_attrs_all set %r=%r", k, child_attrs_all) ti[k] = ti[k].customize(**child_attrs_all) if retval.__extends__ is not None: retval.__extends__ = retval.__extends__.customize( child_attrs_all=child_attrs_all) retval.Attributes._delayed_child_attrs_all = child_attrs_all if child_attrs is not None: ti = retval._type_info logger.debug("processing child_attrs for %r", cls) for k, v in list(child_attrs.items()): if k in ti: logger.debug(" child_attr set %r=%r", k, v) ti[k] = ti[k].customize(**v) del child_attrs[k] base_fti = {} if retval.__extends__ is not None: retval.__extends__ = retval.__extends__.customize( child_attrs=child_attrs) base_fti = retval.__extends__.get_flat_type_info(retval.__extends__) for k, v in child_attrs.items(): if k not in base_fti: logger.debug(" child_attr delayed %r=%r", k, v) retval.Attributes._delayed_child_attrs[k] = v def recust_selfref(selfref, cls): if len(selfref.customize_args) > 0 or len(selfref.customize_kwargs) > 0: logger.debug("Replace self reference with %r with *%r and **%r", cls, selfref.customize_args, selfref.customize_kwargs) return cls.customize(*selfref.customize_args, **selfref.customize_kwargs) logger.debug("Replace self reference with %r", cls) return cls def _set_member_default(inst, key, cls, attr): def_val = attr.default def_fac = attr.default_factory if def_fac is None and def_val is None: return False if def_fac is not None: if six.PY2 and hasattr(def_fac, 'im_func'): # unbound-method error workaround. huh. def_fac = def_fac.im_func dval = def_fac() # should not check for read-only for default values setattr(inst, key, dval) return True if def_val is not None: # should not check for read-only for default values setattr(inst, key, def_val) return True assert False, "Invalid application state" def _is_sqla_array(cls, attr): # inner object is complex ret1 = issubclass(cls, Array) and \ hasattr(cls.get_inner_type(), '_sa_class_manager') # inner object is primitive ret2 = issubclass(cls, Array) and attr.store_as is not None # object is a bare array ret3 = attr.max_occurs > 1 and hasattr(cls, '_sa_class_manager') return ret1 or ret2 or ret3 def _init_member(inst, key, cls, attr): cls_getattr_ret = getattr(inst.__class__, key, None) if isinstance(cls_getattr_ret, property) and cls_getattr_ret.fset is None: return # we skip read-only properties if _set_member_default(inst, key, cls, attr): return # sqlalchemy objects do their own init. if _is_sqla_array(cls, attr): # except the attributes that sqlalchemy doesn't know about if attr.exc_db: setattr(inst, key, None) elif attr.store_as is None: setattr(inst, key, None) return # sqlalchemy objects do their own init. if hasattr(inst.__class__, '_sa_class_manager'): # except the attributes that sqlalchemy doesn't know about if attr.exc_db: setattr(inst, key, None) elif issubclass(cls, ComplexModelBase) and attr.store_as is None: setattr(inst, key, None) return setattr(inst, key, None) class ComplexModelMeta(with_metaclass(Prepareable, type(ModelBase))): """This metaclass sets ``_type_info``, ``__type_name__`` and ``__extends__`` which are going to be used for (de)serialization and schema generation. """ def __new__(cls, cls_name, cls_bases, cls_dict): """This function initializes the class and registers attributes.""" attrs = _gen_attrs(cls_bases, cls_dict) assert issubclass(attrs, ComplexModelBase.Attributes), \ ("%r must be a ComplexModelBase.Attributes subclass" % attrs) cls_dict = _get_ordered_attributes(cls_name, cls_dict, attrs) type_name = cls_dict.get("__type_name__", None) if type_name is None: cls_dict["__type_name__"] = cls_name _type_info = _get_type_info(cls, cls_name, cls_bases, cls_dict, attrs) # used for sub_name and sub_ns _type_info_alt = cls_dict['_type_info_alt'] = TypeInfo() for b in cls_bases: if hasattr(b, '_type_info_alt'): _type_info_alt.update(b._type_info_alt) _sanitize_type_info(cls_name, _type_info, _type_info_alt) _sanitize_sqlalchemy_parameters(cls_dict, attrs) return super(ComplexModelMeta, cls).__new__(cls, cls_name, cls_bases, cls_dict) def __init__(self, cls_name, cls_bases, cls_dict): type_info = self._type_info extends = self.__extends__ if extends is not None and self.__orig__ is None: eattr = extends.Attributes if eattr._subclasses is None: eattr._subclasses = [] eattr._subclasses.append(self) if self.Attributes._subclasses is eattr._subclasses: self.Attributes._subclasses = None # sanitize fields for k, v in type_info.items(): # replace bare SelfRerefence if issubclass(v, SelfReference): self._replace_field(k, recust_selfref(v, self)) # cache XmlData for easier access elif issubclass(v, XmlData): if self.Attributes._xml_tag_body_as is None: self.Attributes._xml_tag_body_as = [(k, v)] else: self.Attributes._xml_tag_body_as.append((k, v)) # replace SelfRerefence in arrays elif issubclass(v, Array): v2, = v._type_info.values() while issubclass(v2, Array): v = v2 v2, = v2._type_info.values() if issubclass(v2, SelfReference): v._set_serializer(recust_selfref(v2, self)) # apply field order # FIXME: Implement this better new_type_info = [] for k, v in self._type_info.items(): if v.Attributes.order == None: new_type_info.append(k) for k, v in self._type_info.items(): if v.Attributes.order is not None: new_type_info.insert(v.Attributes.order, k) assert len(self._type_info) == len(new_type_info) self._type_info.keys()[:] = new_type_info # install checkers for validation on assignment for k, v in self._type_info.items(): if not v.Attributes.validate_on_assignment: continue def _get_prop(self): return self.__dict__[k] def _set_prop(self, val): if not (val is None or isinstance(val, v.Value)): raise ValueError("Invalid value %r, " "should be an instance of %r" % (val, v.Value)) self.__dict__[k] = val setattr(self, k, property(_get_prop, _set_prop)) # process member rpc methods methods = _gen_methods(self, cls_dict) if len(methods) > 0: self.Attributes.methods = methods # finalize sql table mapping tn = self.Attributes.table_name meta = self.Attributes.sqla_metadata t = self.Attributes.sqla_table # For spyne objects reflecting an existing db table if tn is None: if t is not None: self.Attributes.sqla_metadata = t.metadata from spyne.store.relational import gen_spyne_info gen_spyne_info(self) # For spyne objects being converted to a sqlalchemy table elif meta is not None and (tn is not None or t is not None) and \ len(self._type_info) > 0: from spyne.store.relational import gen_sqla_info gen_sqla_info(self, cls_bases) super(ComplexModelMeta, self).__init__(cls_name, cls_bases, cls_dict) # # We record the order fields are defined into ordered dict, so we can # declare them in the same order in the WSDL. # # For Python 3 __prepare__ works out of the box, see PEP 3115. # But we use `Preparable` metaclass for both Python 2 and Python 3 to # support six.add_metaclass decorator # @classmethod def __prepare__(mcs, name, bases, **kwds): return odict() _is_array = lambda v: issubclass(v, Array) or (v.Attributes.max_occurs > 1) class ComplexModelBase(ModelBase): """If you want to make a better class type, this is what you should inherit from. """ __mixin__ = False class Attributes(ModelBase.Attributes): """ComplexModel-specific attributes""" store_as = None """Method for serializing to persistent storage. One of %r. It makes sense to specify this only when this object is a child of another ComplexModel subclass.""" % (PSSM_VALUES,) sqla_metadata = None """None or :class:`sqlalchemy.MetaData` instance.""" sqla_table_args = None """A dict that will be passed to :class:`sqlalchemy.schema.Table` constructor as ``**kwargs``. """ sqla_mapper_args = None """A dict that will be passed to :func:`sqlalchemy.orm.mapper` constructor as. ``**kwargs``. """ sqla_table = None """The sqlalchemy table object""" sqla_mapper = None """The sqlalchemy mapper object""" validate_freq = True """When ``False``, soft validation ignores missing mandatory attributes. """ child_attrs = None """Customize child attributes in one go. It's a dict of dicts. This is ignored unless used via explicit customization.""" child_attrs_all = None """Customize all child attributes. It's a dict. This is ignored unless used via explicit customization. `child_attrs` always take precedence. """ declare_order = None """The order fields of the :class:``ComplexModel`` are to be declared in the SOAP WSDL. If this is left as None or explicitly set to ``'random'`` declares then the fields appear in whatever order the Python's hash map implementation seems fit in the WSDL. This randomised order can change every time the program is run. This is what Spyne <2.11 did if you didn't set _type_info as an explicit sequence (e.g. using a list, odict, etc.). It means that clients who are manually complied or generated from the WSDL will likely need to be recompiled every time it changes. The string ``name`` means the field names are alphabetically sorted in the WSDL declaration. The string ``declared`` means in the order the field type was declared in Python 2, and the order the field was declared in Python 3. In order to get declared field order in Python 2, the :class:`spyne.util.meta.Preparable` class inspects the frame stack in order to locate the class definition, re-parses it to get declaration order from the AST and uses that information to order elements. It's a horrible hack that we tested to work with CPython 2.6 through 3.3 and PyPy. It breaks in Nuitka as Nuitka does away with code objects. Other platforms were not tested. It's not recommended to use set this to ``'declared'`` in Python 2 unless you're sure you fully understand the consequences. """ parent_variant = None """FIXME: document me yo.""" methods = None """A dict of member RPC methods (typically marked with @mrpc).""" method_config_do = None """When not None, it's a callable that accepts a ``@mrpc`` method descriptor and returns a modified version.""" not_wrapped = None """When True, serializes to non-wrapped object, overriding the protocol flag.""" wrapped = None """When True, serializes to a wrapped object, overriding the protocol flag. When a str/bytes/unicode value, uses that value as key wrapper object name.""" _variants = None _xml_tag_body_as = None _delayed_child_attrs = None _delayed_child_attrs_all = None _subclasses = None def __init__(self, *args, **kwargs): cls = self.__class__ cls_attr = cls.Attributes fti = cls.get_flat_type_info(cls) if cls.__orig__ is not None: logger.warning("%r(0x%X) seems to be a customized class. It is not " "supposed to be instantiated. You have been warned.", cls, id(cls)) logger.debug(traceback.format_stack()) if cls_attr._xml_tag_body_as is not None: for arg, (xtba_key, xtba_type) in \ zip(args, cls_attr._xml_tag_body_as): if xtba_key is not None and len(args) == 1: attr = xtba_type.Attributes _init_member(self, xtba_key, xtba_type, attr) self._safe_set(xtba_key, arg, xtba_type, xtba_type.Attributes) elif len(args) > 0: raise TypeError( "Positional argument is only for ComplexModels " "with XmlData field. You must use keyword " "arguments in any other case.") for k, v in fti.items(): attr = v.Attributes if not k in self.__dict__: _init_member(self, k, v, attr) if k in kwargs: self._safe_set(k, kwargs[k], v, attr) def __len__(self): return len(self._type_info) def __getitem__(self, i): if isinstance(i, slice): retval = [] for key in self._type_info.keys()[i]: retval.append(getattr(self, key, None)) else: retval = getattr(self, self._type_info.keys()[i], None) return retval def __repr__(self): return "%s(%s)" % (self.get_type_name(), ', '.join( ['%s=%r' % (k, self.__dict__.get(k)) for k in self.__class__.get_flat_type_info(self.__class__) if self.__dict__.get(k, None) is not None])) def _safe_set(self, key, value, t, attrs): if attrs.read_only: return False try: setattr(self, key, value) except AttributeError as e: logger.exception(e) raise AttributeError("can't set %r attribute %s to %r" % (self.__class__, key, value)) return True @classmethod def get_identifiers(cls): for k, v in cls.get_flat_type_info(cls).items(): if getattr(v.Attributes, 'primary_key', None): yield k, v @classmethod def get_primary_keys(cls): return cls.get_identifiers() def as_dict(self): """Represent object as dict. Null values are omitted from dict representation to support optional not nullable attributes. """ return dict(( (k, getattr(self, k)) for k in self.get_flat_type_info(self.__class__) if getattr(self, k) is not None )) @classmethod def get_serialization_instance(cls, value): """Returns the native object corresponding to the serialized form passed in the ``value`` argument. :param value: This argument can be: * A list or tuple of native types aligned with cls._type_info. * A dict of native types. * The native type itself. If the value type is not a ``list``, ``tuple`` or ``dict``, the value is returned untouched. """ # if the instance is a list, convert it to a cls instance. # this is only useful when deserializing method arguments for a client # request which is the only time when the member order is not arbitrary # (as the members are declared and passed around as sequences of # arguments, unlike dictionaries in a regular class definition). if isinstance(value, list) or isinstance(value, tuple): keys = cls.get_flat_type_info(cls).keys() if not len(value) <= len(keys): logger.error("\n\tcls: %r" "\n\tvalue: %r" "\n\tkeys: %r", cls, value, keys) raise ValueError("Impossible sequence to instance conversion") cls_orig = cls if cls.__orig__ is not None: cls_orig = cls.__orig__ try: inst = cls_orig() except Exception as e: logger.error("Error instantiating %r: %r", cls_orig, e) raise for i in range(len(value)): setattr(inst, keys[i], value[i]) elif isinstance(value, dict): cls_orig = cls if cls.__orig__ is not None: cls_orig = cls.__orig__ inst = cls_orig() for k in cls.get_flat_type_info(cls): setattr(inst, k, value.get(k, None)) else: inst = value return inst @classmethod def get_deserialization_instance(cls, ctx): """Get an empty native type so that the deserialization logic can set its attributes. """ if cls.__orig__ is None: return cls() return cls.__orig__() @classmethod @memoize_id def get_subclasses(cls): retval = [] subca = cls.Attributes._subclasses if subca is not None: retval.extend(subca) for subc in subca: retval.extend(subc.get_subclasses()) return retval @staticmethod @memoize_ignore_none def get_flat_type_info(cls): """Returns a _type_info dict that includes members from all base classes. It's called a "flat" dict because it flattens all members from the inheritance hierarchy into one dict. """ return _get_flat_type_info(cls, TypeInfo()) @classmethod def get_orig(cls): return cls.__orig__ or cls @staticmethod def get_simple_type_info(cls, hier_delim="."): """Returns a _type_info dict that includes members from all base classes and whose types are only primitives. It will prefix field names in non-top-level complex objects with field name of its parent. For example, given hier_delim='.'; the following hierarchy: :: {'some_object': [{'some_string': ['abc']}]} would be transformed to: :: {'some_object.some_string': ['abc']} :param hier_delim: String that will be used as delimiter between field names. Default is ``'.'``. """ return ComplexModelBase.get_simple_type_info_with_prot( cls, hier_delim=hier_delim) @staticmethod @memoize def get_simple_type_info_with_prot(cls, prot=None, hier_delim="."): """See :func:ComplexModelBase.get_simple_type_info""" fti = cls.get_flat_type_info(cls) retval = TypeInfo() tags = set() queue = deque() if prot is None: for k, v in fti.items(): sub_name = k queue.append(( (k,), v, (sub_name,), (_is_array(v),), cls, )) else: for k, v in fti.items(): cls_attrs = prot.get_cls_attrs(v) sub_name = cls_attrs.sub_name if sub_name is None: sub_name = k queue.append(( (k,), v, (sub_name,), (_is_array(v),), cls, )) tags.add(cls) while len(queue) > 0: keys, v, prefix, is_array, parent = queue.popleft() k = keys[-1] if issubclass(v, Array) and v.Attributes.max_occurs == 1: v, = v._type_info.values() key = hier_delim.join(prefix) if issubclass(v, ComplexModelBase): retval[key] = _SimpleTypeInfoElement( path=keys, parent=parent, type_=v, is_array=tuple(is_array), can_be_empty=True, ) if not (v in tags): tags.add(v) if prot is None: for k2, v2 in v.get_flat_type_info(v).items(): sub_name = k2 queue.append(( keys + (k2,), v2, prefix + (sub_name,), is_array + (_is_array(v),), v )) else: for k2, v2 in v.get_flat_type_info(v).items(): cls_attrs = prot.get_cls_attrs(v2) sub_name = cls_attrs.sub_name if sub_name is None: sub_name = k2 queue.append(( keys + (k2,), v2, prefix + (sub_name,), is_array + (_is_array(v),), v, )) else: value = retval.get(key, None) if value is not None: raise ValueError("%r.%s conflicts with %r" % (cls, k, value.path)) retval[key] = _SimpleTypeInfoElement( path=keys, parent=parent, type_=v, is_array=tuple(is_array), can_be_empty=False, ) return retval @staticmethod def resolve_namespace(cls, default_ns, tags=None): if tags is None: tags = set() elif cls in tags: return False if not ModelBase.resolve_namespace(cls, default_ns, tags): return False for k, v in cls._type_info.items(): if v is None: continue if v.__type_name__ is ModelBase.Empty: v._fill_empty_type_name(cls.get_namespace(), cls.get_type_name(), k) v.resolve_namespace(v, default_ns, tags) if cls._force_own_namespace is not None: for c in cls._force_own_namespace: c.__namespace__ = cls.get_namespace() ComplexModel.resolve_namespace(c, cls.get_namespace(), tags) assert not (cls.__namespace__ is ModelBase.Empty) assert not (cls.__type_name__ is ModelBase.Empty) return True @staticmethod def produce(namespace, type_name, members): """Lets you create a class programmatically.""" return ComplexModelMeta(type_name, (ComplexModel,), odict({ '__namespace__': namespace, '__type_name__': type_name, '_type_info': TypeInfo(members), })) @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class. Because each class is registered as a variant of the original (__orig__) class, using this function to generate classes dynamically on-the-fly could cause memory leaks. You have been warned. """ store_as = apply_pssm(kwargs.get('store_as', None)) if store_as is not None: kwargs['store_as'] = store_as cls_name, cls_bases, cls_dict = cls._s_customize(**kwargs) cls_dict['__module__'] = cls.__module__ if '__extends__' not in cls_dict: cls_dict['__extends__'] = cls.__extends__ retval = type(cls_name, cls_bases, cls_dict) retval._type_info = TypeInfo(cls._type_info) retval.__type_name__ = cls.__type_name__ retval.__namespace__ = cls.__namespace__ retval.Attributes.parent_variant = cls dca = retval.Attributes._delayed_child_attrs if retval.Attributes._delayed_child_attrs is None: retval.Attributes._delayed_child_attrs = {} else: retval.Attributes._delayed_child_attrs = dict(dca.items()) tn = kwargs.get("type_name", None) if tn is not None: retval.__type_name__ = tn ns = kwargs.get("namespace", None) if ns is not None: retval.__namespace__ = ns if cls is not ComplexModel: cls._process_variants(retval) _process_child_attrs(cls, retval, kwargs) # we could be smarter, but customize is supposed to be called only # during daemon initialization, so it's not really necessary. ComplexModelBase.get_subclasses.memo.clear() ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info_with_prot.memo.clear() return retval @classmethod def _process_variants(cls, retval): orig = getattr(retval, '__orig__', None) if orig is not None: if orig.Attributes._variants is None: orig.Attributes._variants = WeakKeyDictionary() orig.Attributes._variants[retval] = True # _variants is only for the root class. retval.Attributes._variants = None @classmethod def _append_field_impl(cls, field_name, field_type): assert isinstance(field_name, string_types) dcaa = cls.Attributes._delayed_child_attrs_all if dcaa is not None: field_type = field_type.customize(**dcaa) dca = cls.Attributes._delayed_child_attrs if dca is not None: d_cust = dca.get(field_name, None) if d_cust is not None: field_type = field_type.customize(**d_cust) cls._type_info[field_name] = field_type ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info_with_prot.memo.clear() @classmethod def _append_to_variants(cls, field_name, field_type): if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c.append_field(field_name, field_type) @classmethod def append_field(cls, field_name, field_type): cls._append_field_impl(field_name, field_type) cls._append_to_variants(field_name, field_type) @classmethod def _insert_to_variants(cls, index, field_name, field_type): if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c.insert_field(index, field_name, field_type) @classmethod def _insert_field_impl(cls, index, field_name, field_type): assert isinstance(index, int) assert isinstance(field_name, string_types) dcaa = cls.Attributes._delayed_child_attrs_all if dcaa is not None: field_type = field_type.customize(**dcaa) dca = cls.Attributes._delayed_child_attrs if dca is not None: if field_name in dca: d_cust = dca.pop(field_name) field_type = field_type.customize(**d_cust) cls._type_info.insert(index, (field_name, field_type)) ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info_with_prot.memo.clear() @classmethod def insert_field(cls, index, field_name, field_type): cls._insert_field_impl(index, field_name, field_type) cls._insert_to_variants(index, field_name, field_type) @classmethod def _replace_in_variants(cls, field_name, field_type): if cls.Attributes._variants is not None: for c in cls.Attributes._variants: c._replace_field(field_name, field_type) @classmethod def _replace_field_impl(cls, field_name, field_type): assert isinstance(field_name, string_types) cls._type_info[field_name] = field_type ComplexModelBase.get_flat_type_info.memo.clear() ComplexModelBase.get_simple_type_info_with_prot.memo.clear() @classmethod def _replace_field(cls, field_name, field_type): cls._replace_field_impl(field_name, field_type) cls._replace_in_variants(field_name, field_type) @classmethod def store_as(cls, what): return cls.customize(store_as=what) @classmethod def novalidate_freq(cls): return cls.customize(validate_freq=False) @classmethod def init_from(cls, other, **kwargs): retval = (cls if cls.__orig__ is None else cls.__orig__)() for k, v in cls.get_flat_type_info(cls).items(): try: if k in kwargs: retval._safe_set(k, kwargs[k], v, v.Attributes) elif hasattr(other, k): retval._safe_set(k, getattr(other, k), v, v.Attributes) except AttributeError as e: logger.warning("Error setting %s: %r", k, e) return retval @classmethod def __respawn__(cls, ctx=None, filters=None): if ctx is not None and ctx.in_object is not None and \ len(ctx.in_object) > 0: retval = next(iter(ctx.in_object)) if retval is not None: return retval if ctx.descriptor.default_on_null: return cls.get_deserialization_instance(ctx) @add_metaclass(ComplexModelMeta) class ComplexModel(ComplexModelBase): """The general complexType factory. The __call__ method of this class will return instances, contrary to primivites where the same call will result in customized duplicates of the original class definition. Those who'd like to customize the class should use the customize method. (see :class:``spyne.model.ModelBase``). """ @add_metaclass(ComplexModelMeta) class Array(ComplexModelBase): """This class generates a ComplexModel child that has one attribute that has the same name as the serialized class. It's contained in a Python list. """ class Attributes(ComplexModelBase.Attributes): _wrapper = True def __new__(cls, serializer, member_name=None, wrapped=True, **kwargs): if not wrapped: if serializer.Attributes.max_occurs == 1: kwargs['max_occurs'] = 'unbounded' return serializer.customize(**kwargs) retval = cls.customize(**kwargs) _serializer = _get_spyne_type(cls.__name__, '__serializer__', serializer) if _serializer is None: raise ValueError("serializer=%r is not a valid spyne type" % serializer) if issubclass(_serializer, SelfReference): # hack to make sure the array passes ComplexModel sanity checks # that are there to prevent empty arrays. retval._type_info = {'_bogus': _serializer} else: retval._set_serializer(_serializer, member_name) tn = kwargs.get("type_name", None) if tn is not None: retval.__type_name__ = tn return retval @classmethod def _fill_empty_type_name(cls, parent_ns, parent_tn, k): cls.__namespace__ = parent_ns tn = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX) child_v, = cls._type_info.values() child_v.__type_name__ = tn cls._type_info = TypeInfo({tn: child_v}) cls.__type_name__ = '%s%s%s' % (const.ARRAY_PREFIX, tn, const.ARRAY_SUFFIX) extends = child_v.__extends__ while extends is not None and extends.get_type_name() is cls.Empty: extends._fill_empty_type_name(parent_ns, parent_tn, k + const.PARENT_SUFFIX) extends = extends.__extends__ @classmethod def customize(cls, **kwargs): serializer_attrs = kwargs.get('serializer_attrs', None) if serializer_attrs is None: return super(Array, cls).customize(**kwargs) del kwargs['serializer_attrs'] logger.debug('Pass serializer attrs %r', serializer_attrs) serializer, = cls._type_info.values() return cls(serializer.customize(**serializer_attrs)).customize(**kwargs) @classmethod def _set_serializer(cls, serializer, member_name=None): if serializer.get_type_name() is ModelBase.Empty: # A customized class member_name = "OhNoes" # mark array type name as "to be resolved later". cls.__type_name__ = ModelBase.Empty else: if member_name is None: member_name = serializer.get_type_name() cls.__type_name__ = '%s%s%s' % (const.ARRAY_PREFIX, serializer.get_type_name(), const.ARRAY_SUFFIX) # hack to default to unbounded arrays when the user didn't specify # max_occurs. if serializer.Attributes.max_occurs == 1: serializer = serializer.customize(max_occurs=decimal.Decimal('inf')) assert isinstance(member_name, string_types), member_name cls._type_info = TypeInfo({member_name: serializer}) # the array belongs to its child's namespace, it doesn't have its own # namespace. @staticmethod def resolve_namespace(cls, default_ns, tags=None): (serializer,) = cls._type_info.values() serializer.resolve_namespace(serializer, default_ns, tags) if cls.__namespace__ is None: cls.__namespace__ = serializer.get_namespace() if cls.__namespace__ in PREFMAP: cls.__namespace__ = default_ns return ComplexModel.resolve_namespace(cls, default_ns, tags) @classmethod def get_serialization_instance(cls, value): inst = ComplexModel.__new__(Array) (member_name,) = cls._type_info.keys() setattr(inst, member_name, value) return inst @classmethod def get_deserialization_instance(cls, ctx): return [] @classmethod def get_inner_type(cls): return next(iter(cls._type_info.values())) class Iterable(Array): """This class generates a ``ComplexModel`` child that has one attribute that has the same name as the serialized class. It's contained in a Python iterable. The distinction with the ``Array`` is made in the protocol implementation, this is just a marker. Whenever you return a generator instead of a list, you should use this type as this suggests the intermediate machinery to NEVER actually try to iterate over the value. An ``Array`` could be iterated over for e.g. logging purposes. """ class Attributes(Array.Attributes): logged = False class Push(PushBase): """The push interface to the `Iterable`. Anything append()'ed to a `Push` instance is serialized and written to outgoing stream immediately. When using Twisted, Push callbacks are called from the reactor thread if the instantiation is done in a reactor thread. Otherwise, callbacks are called by `deferToThread`. Make sure to avoid relying on thread-local stuff as `deferToThread` is not guaranteed to restore original thread context. """ pass def TTableModelBase(): from spyne.store.relational import add_column class TableModelBase(ComplexModelBase): @classmethod def append_field(cls, field_name, field_type): cls._append_field_impl(field_name, field_type) # There could have been changes to field_type in ComplexModel so we # should not use field_type directly from above if cls.__table__ is not None: add_column(cls, field_name, cls._type_info[field_name]) cls._append_to_variants(field_name, field_type) @classmethod def replace_field(cls, field_name, field_type): raise NotImplementedError() @classmethod def insert_field(cls, index, field_name, field_type): cls._insert_field_impl(index, field_name, field_type) # There could have been changes to field_type in ComplexModel so we # should not use field_type directly from above if cls.__table__ is not None: add_column(cls, field_name, cls._type_info[field_name]) cls._insert_to_variants(index, field_name, field_type) return TableModelBase # this has docstring repeated in the documentation at reference/model/complex.rst def TTableModel(metadata=None, base=None, metaclass=None): """A TableModel template that generates a new TableModel class for each call. If metadata is not supplied, a new one is instantiated. """ from sqlalchemy import MetaData if base is None: base = TTableModelBase() if metaclass is None: metaclass = ComplexModelMeta @add_metaclass(metaclass) class TableModel(base): class Attributes(ComplexModelBase.Attributes): sqla_metadata = metadata if metadata is not None else MetaData() return TableModel def Mandatory(cls, **_kwargs): """Customizes the given type to be a mandatory one. Has special cases for :class:`spyne.model.primitive.Unicode` and :class:`spyne.model.complex.Array`\\. """ kwargs = dict(min_occurs=1, nillable=False) if cls.get_type_name() is not cls.Empty: kwargs['type_name'] = '%s%s%s' % (const.MANDATORY_PREFIX, cls.get_type_name(), const.MANDATORY_SUFFIX) kwargs.update(_kwargs) if issubclass(cls, Unicode): kwargs.update(dict(min_len=1)) elif issubclass(cls, Array): (k,v), = cls._type_info.items() if v.Attributes.min_occurs == 0: cls._type_info[k] = Mandatory(v) return cls.customize(**kwargs) spyne-spyne-2.14.0/spyne/model/enum.py000066400000000000000000000067271417664205300176700ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.model import SimpleModel # adapted from: http://code.activestate.com/recipes/413486/ class EnumBase(SimpleModel): __namespace__ = None @staticmethod def resolve_namespace(cls, default_ns, tags=None): if cls.__namespace__ is None: cls.__namespace__ = default_ns return True @staticmethod def validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and value in cls.__values__ ) def Enum(*values, **kwargs): """The enum type that can only return ``True`` when compared to types of own type. Here's how it's supposed to work: >>> from spyne.model.enum import Enum >>> SomeEnum = Enum("SomeValue", "SomeOtherValue", type_name="SomeEnum") >>> SomeEnum.SomeValue == SomeEnum.SomeOtherValue False >>> SomeEnum.SomeValue == SomeEnum.SomeValue True >>> SomeEnum.SomeValue is SomeEnum.SomeValue True >>> SomeEnum.SomeValue == 0 False >>> SomeEnum2 = Enum("SomeValue", "SomeOtherValue", type_name="SomeEnum") >>> SomeEnum2.SomeValue == SomeEnum.SomeValue False In the above example, ``SomeEnum`` can be used as a regular Spyne model. """ type_name = kwargs.get('type_name', None) docstr = kwargs.get('doc', '') if type_name is None: raise Exception("Please specify 'type_name' as a keyword argument") assert len(values) > 0, "Empty enums are meaningless" maximum = len(values) # to make __invert__ work class EnumValue(object): __slots__ = ('__value',) def __init__(self, value): self.__value = value def __hash__(self): return hash(self.__value) def __cmp__(self, other): if isinstance(self, type(other)): return cmp(self.__value, other.__value) else: return cmp(id(self), id(other)) def __invert__(self): return values[maximum - self.__value] def __nonzero__(self): return bool(self.__value) def __bool__(self): return bool(self.__value) def __repr__(self): return str(values[self.__value]) class EnumType(EnumBase): __doc__ = docstr __type_name__ = type_name __values__ = values def __iter__(self): return iter(values) def __len__(self): return len(values) def __getitem__(self, i): return values[i] def __repr__(self): return 'Enum' + str(enumerate(values)) def __str__(self): return 'enum ' + str(values) for i, v in enumerate(values): setattr(EnumType, v, EnumValue(i)) return EnumType spyne-spyne-2.14.0/spyne/model/fault.py000066400000000000000000000135441417664205300200320ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from warnings import warn from collections import defaultdict import spyne.const from spyne.model.primitive import Any from spyne.util.six import add_metaclass from spyne.model.complex import ComplexModelMeta from spyne.model.complex import ComplexModelBase class FaultMeta(ComplexModelMeta): def __init__(self, cls_name, cls_bases, cls_dict): super(FaultMeta, self).__init__(cls_name, cls_bases, cls_dict) code = cls_dict.get('CODE', None) if code is not None: target = Fault.REGISTERED[code] target.add(self) if spyne.const.WARN_ON_DUPLICATE_FAULTCODE and len(target) > 1: warn("Duplicate faultcode {} detected for classes {}" .format(code, target)) @add_metaclass(FaultMeta) class Fault(ComplexModelBase, Exception): """Use this class as a base for all public exceptions. The Fault object adheres to the `SOAP 1.1 Fault definition `_, which has three main attributes: :param faultcode: It's a dot-delimited string whose first fragment is either 'Client' or 'Server'. Just like HTTP 4xx and 5xx codes, 'Client' indicates that something was wrong with the input, and 'Server' indicates something went wrong during the processing of an otherwise legitimate request. Protocol implementors should heed the values in ``faultcode`` to set proper return codes in the protocol level when necessary. E.g. HttpRpc protocol will return a HTTP 404 error when a :class:`spyne.error.ResourceNotFound` is raised, and a general HTTP 400 when the ``faultcode`` starts with ``'Client.'`` or is ``'Client'``. Soap would return Http 500 for any kind of exception, and denote the nature of the exception in the Soap response body. (because that's what the standard says... Yes, soap is famous for a reason :)) :param faultstring: It's the human-readable explanation of the exception. :param detail: Additional information dict. :param lang: Language code corresponding to the language of faultstring. """ REGISTERED = defaultdict(set) """Class-level variable that holds a multimap of all fault codes and the associated classes.""" __type_name__ = "Fault" CODE = None def __init__(self, faultcode='Server', faultstring="", faultactor="", detail=None, lang=spyne.DEFAULT_LANGUAGE): self.faultcode = faultcode self.faultstring = faultstring or self.get_type_name() self.faultactor = faultactor self.detail = detail self.lang = lang def __len__(self): return 1 def __str__(self): return repr(self) def __repr__(self): if self.detail is None: return "%s(%s: %r)" % (self.__class__.__name__, self.faultcode, self.faultstring) return "%s(%s: %r detail: %r)" % (self.__class__.__name__, self.faultcode, self.faultstring, self.detail) @staticmethod def to_dict(cls, value, prot): if not issubclass(cls, Fault): return { "faultcode": "Server.Unknown", "faultstring": cls.__name__, "detail": str(value), } retval = { "faultcode": value.faultcode, "faultstring": value.faultstring, } if value.faultactor is not None: if len(value.faultactor) > 0 or (not prot.ignore_empty_faultactor): retval["faultactor"] = value.faultactor if value.detail is not None: retval["detail"] = value.detail_to_doc(prot) return retval # # From http://schemas.xmlsoap.org/soap/envelope/ # # # # # # @staticmethod def to_list(cls, value, prot=None): if not issubclass(cls, Fault): return [ "Server.Unknown", # faultcode cls.__name__, # faultstring "", # faultactor str(value), # detail ] retval = [ value.faultcode, value.faultstring, ] if value.faultactor is not None: retval.append(value.faultactor) else: retval.append("") if value.detail is not None: retval.append(value.detail_to_doc(prot)) else: retval.append("") return retval @classmethod def to_bytes_iterable(cls, value): return [ value.faultcode.encode('utf8'), b'\n\n', value.faultstring.encode('utf8'), ] def detail_to_doc(self, prot): return self.detail def detail_from_doc(self, prot, doc): self.detail = doc spyne-spyne-2.14.0/spyne/model/primitive/000077500000000000000000000000001417664205300203465ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/model/primitive/__init__.py000066400000000000000000000162141417664205300224630ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # NATIVE_MAP = {} string_encoding = 'UTF-8' # ??? from spyne.model.primitive._base import Any from spyne.model.primitive._base import AnyDict from spyne.model.primitive._base import AnyHtml from spyne.model.primitive._base import AnyXml from spyne.model.primitive._base import Boolean from spyne.model.primitive.string import Unicode from spyne.model.primitive.string import String from spyne.model.primitive.string import AnyUri from spyne.model.primitive.string import Uuid from spyne.model.primitive.string import ImageUri from spyne.model.primitive.string import Ltree from spyne.model.primitive.string import MimeType from spyne.model.primitive.string import MimeTypeStrict from spyne.model.primitive.string import MediaType from spyne.model.primitive.string import MediaTypeStrict from spyne.model.primitive.xml import ID from spyne.model.primitive.xml import Token from spyne.model.primitive.xml import NMToken from spyne.model.primitive.xml import Name from spyne.model.primitive.xml import NCName from spyne.model.primitive.xml import QName from spyne.model.primitive.xml import Language from spyne.model.primitive.xml import NormalizedString from spyne.model.primitive.spatial import Point from spyne.model.primitive.spatial import Line from spyne.model.primitive.spatial import LineString from spyne.model.primitive.spatial import Polygon from spyne.model.primitive.spatial import MultiPoint from spyne.model.primitive.spatial import MultiLine from spyne.model.primitive.spatial import MultiLineString from spyne.model.primitive.spatial import MultiPolygon # Date/Time types from spyne.model.primitive.datetime import Date from spyne.model.primitive.datetime import DateTime from spyne.model.primitive.datetime import Duration from spyne.model.primitive.datetime import Time # Numbers from spyne.model.primitive.number import Decimal from spyne.model.primitive.number import Double from spyne.model.primitive.number import Float from spyne.model.primitive.number import Integer8 from spyne.model.primitive.number import Byte from spyne.model.primitive.number import Integer16 from spyne.model.primitive.number import Short from spyne.model.primitive.number import Integer32 from spyne.model.primitive.number import Int from spyne.model.primitive.number import Integer64 from spyne.model.primitive.number import Long from spyne.model.primitive.number import Integer from spyne.model.primitive.number import NumberLimitsWarning from spyne.model.primitive.number import UnsignedInteger8 from spyne.model.primitive.number import UnsignedByte from spyne.model.primitive.number import UnsignedInteger16 from spyne.model.primitive.number import UnsignedShort from spyne.model.primitive.number import UnsignedInteger32 from spyne.model.primitive.number import UnsignedInt from spyne.model.primitive.number import UnsignedInteger64 from spyne.model.primitive.number import UnsignedLong from spyne.model.primitive.number import NonNegativeInteger # Xml Schema calls it so from spyne.model.primitive.number import UnsignedInteger from spyne.model.primitive.network import MacAddress from spyne.model.primitive.network import IpAddress from spyne.model.primitive.network import Ipv4Address from spyne.model.primitive.network import Ipv6Address # This class is DEPRECATED. Use the spyne.model.Mandatory like this: # >>> from spyne.model import Mandatory as M, Unicode # >>> MandatoryEmail = M(Unicode(pattern='[^@]+@[^@]+')) class Mandatory: Unicode = Unicode(type_name="MandatoryString", min_occurs=1, nillable=False, min_len=1) String = String(type_name="MandatoryString", min_occurs=1, nillable=False, min_len=1) AnyXml = AnyXml(type_name="MandatoryXml", min_occurs=1, nillable=False) AnyDict = AnyDict(type_name="MandatoryDict", min_occurs=1, nillable=False) AnyUri = AnyUri(type_name="MandatoryUri", min_occurs=1, nillable=False, min_len=1) ImageUri = ImageUri(type_name="MandatoryImageUri", min_occurs=1, nillable=False, min_len=1) Boolean = Boolean(type_name="MandatoryBoolean", min_occurs=1, nillable=False) Date = Date(type_name="MandatoryDate", min_occurs=1, nillable=False) Time = Time(type_name="MandatoryTime", min_occurs=1, nillable=False) DateTime = DateTime(type_name="MandatoryDateTime", min_occurs=1, nillable=False) Duration = Duration(type_name="MandatoryDuration", min_occurs=1, nillable=False) Decimal = Decimal(type_name="MandatoryDecimal", min_occurs=1, nillable=False) Double = Double(type_name="MandatoryDouble", min_occurs=1, nillable=False) Float = Float(type_name="MandatoryFloat", min_occurs=1, nillable=False) Integer = Integer(type_name="MandatoryInteger", min_occurs=1, nillable=False) Integer64 = Integer64(type_name="MandatoryLong", min_occurs=1, nillable=False) Integer32 = Integer32(type_name="MandatoryInt", min_occurs=1, nillable=False) Integer16 = Integer16(type_name="MandatoryShort", min_occurs=1, nillable=False) Integer8 = Integer8(type_name="MandatoryByte", min_occurs=1, nillable=False) Long = Integer64 Int = Integer32 Short = Integer16 Byte = Integer8 UnsignedInteger = UnsignedInteger(type_name="MandatoryUnsignedInteger", min_occurs=1, nillable=False) UnsignedInteger64 = UnsignedInteger64(type_name="MandatoryUnsignedLong", min_occurs=1, nillable=False) UnsignedInteger32 = UnsignedInteger32(type_name="MandatoryUnsignedInt", min_occurs=1, nillable=False) UnsignedInteger16 = UnsignedInteger16(type_name="MandatoryUnsignedShort", min_occurs=1, nillable=False) UnsignedInteger8 = UnsignedInteger8(type_name="MandatoryUnsignedByte", min_occurs=1, nillable=False) UnsignedLong = UnsignedInteger64 UnsignedInt = UnsignedInteger32 UnsignedShort = UnsignedInteger16 UnsignedByte = UnsignedInteger8 Uuid = Uuid(type_name="MandatoryUuid", min_len=1, min_occurs=1, nillable=False) Point = Point(type_name="Point", min_len=1, min_occurs=1, nillable=False) Line = Line(type_name="LineString", min_len=1, min_occurs=1, nillable=False) LineString = Line Polygon = Polygon(type_name="Polygon", min_len=1, min_occurs=1, nillable=False) MultiPoint = MultiPoint(type_name="MandatoryMultiPoint", min_len=1, min_occurs=1, nillable=False) MultiLine = MultiLine(type_name="MandatoryMultiLineString", min_len=1, min_occurs=1, nillable=False) MultiLineString = MultiLine MultiPolygon = MultiPolygon(type_name="MandatoryMultiPolygon", min_len=1, min_occurs=1, nillable=False) assert Mandatory.Long == Mandatory.Integer64 spyne-spyne-2.14.0/spyne/model/primitive/_base.py000066400000000000000000000075151417664205300220010ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ The ``spyne.model.primitive`` package contains types with values that fit in a single field. See :mod:`spyne.protocol._model` for {to,from}_string implementations. """ from __future__ import absolute_import from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP from spyne.model._base import apply_pssm, msgpack, xml, json def re_match_with_span(attr, value): if attr.pattern is None: return True m = attr._pattern_re.match(value) # if m: # print(m, m.span(), len(value)) # else: # print(m) return (m is not None) and (m.span() == (0, len(value))) class AnyXml(SimpleModel): """An xml node that can contain any number of sub nodes. It's represented by an ElementTree object.""" __type_name__ = 'anyType' class Attributes(SimpleModel.Attributes): namespace = None """Xml-Schema specific namespace attribute""" process_contents = None """Xml-Schema specific processContents attribute""" class Any(SimpleModel): @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" store_as = apply_pssm(kwargs.get('store_as', None)) if store_as is not None: kwargs['store_as'] = store_as return super(Any, cls).customize(**kwargs) class AnyHtml(SimpleModel): __type_name__ = 'string' @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" store_as = apply_pssm(kwargs.get('store_as', None)) if store_as is not None: kwargs['store_as'] = store_as return super(AnyHtml, cls).customize(**kwargs) class AnyDict(SimpleModel): """A dict instance that can contain other dicts, iterables or primitive types. Its serialization is protocol-dependent. """ __type_name__ = 'anyType' Value = dict class Attributes(SimpleModel.Attributes): store_as = None """Method for serializing to persistent storage. One of 'xml', 'json', 'jsonb', 'msgpack'. It makes sense to specify this only when this object belongs to a `ComplexModel` sublass.""" @classmethod def customize(cls, **kwargs): """Duplicates cls and overwrites the values in ``cls.Attributes`` with ``**kwargs`` and returns the new class.""" store_as = apply_pssm(kwargs.get('store_as', None)) if store_as is not None: kwargs['store_as'] = store_as return super(AnyDict, cls).customize(**kwargs) class Boolean(SimpleModel): """Life is simple here. Just true or false.""" class Attributes(SimpleModel.Attributes): store_as = bool """Method for serializing to persistent storage. One of `bool` or `int` builtins. It makes sense to specify this only when this object belongs to a `ComplexModel` sublass.""" __type_name__ = 'boolean' NATIVE_MAP.update({ bool: Boolean, }) spyne-spyne-2.14.0/spyne/model/primitive/datetime.py000066400000000000000000000243141417664205300225200ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import import re import spyne import datetime from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP FLOAT_PATTERN = r'-?[0-9]+\.?[0-9]*(e-?[0-9]+)?' DATE_PATTERN = r'(?P\d{4})-(?P\d{2})-(?P\d{2})' TIME_PATTERN = r'(?P
\d{2}):(?P\d{2}):(?P\d{2})(?P\.\d+)?' OFFSET_PATTERN = r'(?P[+-]\d{2}):(?P\d{2})' DATETIME_PATTERN = DATE_PATTERN + '[T ]' + TIME_PATTERN class Time(SimpleModel): """Just that, Time. No time zone support. Native type is :class:`datetime.time`. """ __type_name__ = 'time' Value = datetime.time class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Time` type.""" gt = None # minExclusive """The time should be greater than this time.""" ge = datetime.time(0, 0, 0, 0) # minInclusive """The time should be greater than or equal to this time.""" lt = None # maxExclusive """The time should be lower than this time.""" le = datetime.time(23, 59, 59, 999999) # maxInclusive """The time should be lower than or equal to this time.""" pattern = None """A regular expression that matches the whole time. See here for more info: http://www.regular-expressions.info/xml.html""" time_format = None """Time format fed to the ``strftime`` function. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how Date objects should be serialized.""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Time.Attributes.gt and cls.Attributes.ge == Time.Attributes.ge and cls.Attributes.lt == Time.Attributes.lt and cls.Attributes.le == Time.Attributes.le and cls.Attributes.pattern == Time.Attributes.pattern ) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) and ( value is None or ( (cls.Attributes.gt is None or value > cls.Attributes.gt) and value >= cls.Attributes.ge and (cls.Attributes.lt is None or value < cls.Attributes.lt) and value <= cls.Attributes.le )) _min_dt = datetime.datetime.min.replace(tzinfo=spyne.LOCAL_TZ) _max_dt = datetime.datetime.max.replace(tzinfo=spyne.LOCAL_TZ) class DateTime(SimpleModel): """A compact way to represent dates and times together. Supports time zones. Working with timezones is a bit quirky -- Spyne works very hard to have all datetimes with time zones internally and only strips them when explicitly requested with ``timezone=False``\\. See :attr:`DateTime.Attributes.as_timezone` for more information. Native type is :class:`datetime.datetime`. """ __type_name__ = 'dateTime' Value = datetime.datetime _local_re = re.compile(DATETIME_PATTERN) _utc_re = re.compile(DATETIME_PATTERN + 'Z') _offset_re = re.compile(DATETIME_PATTERN + OFFSET_PATTERN) class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.DateTime` type.""" gt = None # minExclusive """The datetime should be greater than this datetime. It must always have a timezone.""" ge = _min_dt # minInclusive """The datetime should be greater than or equal to this datetime. It must always have a timezone.""" lt = None # maxExclusive """The datetime should be lower than this datetime. It must always have a timezone.""" le = _max_dt # maxInclusive """The datetime should be lower than or equal to this datetime. It must always have a timezone.""" pattern = None """A regular expression that matches the whole datetime. See here for more info: http://www.regular-expressions.info/xml.html""" dt_format = None """DateTime format fed to the ``strftime`` function. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how DateTime objects should be serialized.""" out_format = None """DateTime format fed to the ``strftime`` function only when serializing. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how DateTime objects should be serialized.""" string_format = None """A regular python string formatting string. %s will contain the date string. See here for more info: http://docs.python.org/library/stdtypes.html#string-formatting""" as_timezone = None """When not None, converts: - Outgoing values to the given time zone (by calling ``.astimezone()``). - Incoming values without tzinfo to the given time zone by calling ``.replace(tzinfo=)`` and values with tzinfo to the given timezone by calling ``.astimezone()``. Either None or a return value of pytz.timezone() When this is None and a datetime with tzinfo=None comes in, it's converted to spyne.LOCAL_TZ which defaults to ``pytz.utc``. You can use `tzlocal `_ to set it to local time right after ``import spyne``. """ timezone = True """If False, time zone info is stripped before serialization. Also makes sqlalchemy schema generator emit 'timestamp without timezone'.""" serialize_as = None """One of (None, 'sec', 'sec_float', 'msec', 'msec_float', 'usec')""" # TODO: Move this to ModelBase and make it work with all types in all # protocols. parser = None """Callable for string parser. It must accept exactly four arguments: `protocol, cls, string` and must return a `datetime.datetime` object. If this is not None, all other parsing configurations (e.g. `date_format`) are ignored. """ @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == DateTime.Attributes.gt and cls.Attributes.ge == DateTime.Attributes.ge and cls.Attributes.lt == DateTime.Attributes.lt and cls.Attributes.le == DateTime.Attributes.le and cls.Attributes.pattern == DateTime.Attributes.pattern ) @staticmethod def validate_native(cls, value): if isinstance(value, datetime.datetime) and value.tzinfo is None: value = value.replace(tzinfo=spyne.LOCAL_TZ) return SimpleModel.validate_native(cls, value) and ( value is None or ( # min_dt is also a valid value if gt is intact. (cls.Attributes.gt is None or value > cls.Attributes.gt) and value >= cls.Attributes.ge # max_dt is also a valid value if lt is intact. and (cls.Attributes.lt is None or value < cls.Attributes.lt) and value <= cls.Attributes.le )) class Date(DateTime): """Just that, Date. No time zone support. Native type is :class:`datetime.date`. """ __type_name__ = 'date' _offset_re = re.compile(DATE_PATTERN + '(' + OFFSET_PATTERN + '|Z)') Value = datetime.date class Attributes(DateTime.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Date` type.""" gt = None # minExclusive """The date should be greater than this date.""" ge = datetime.date(1, 1, 1) # minInclusive """The date should be greater than or equal to this date.""" lt = None # maxExclusive """The date should be lower than this date.""" le = datetime.date(datetime.MAXYEAR, 12, 31) # maxInclusive """The date should be lower than or equal to this date.""" date_format = None """Date format fed to the ``strftime`` function. See: http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior Ignored by protocols like SOAP which have their own ideas about how Date objects should be serialized.""" pattern = None """A regular expression that matches the whole date. See here for more info: http://www.regular-expressions.info/xml.html""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Date.Attributes.gt and cls.Attributes.ge == Date.Attributes.ge and cls.Attributes.lt == Date.Attributes.lt and cls.Attributes.le == Date.Attributes.le and cls.Attributes.pattern == Date.Attributes.pattern ) # this object tries to follow ISO 8601 standard. class Duration(SimpleModel): """Native type is :class:`datetime.timedelta`.""" __type_name__ = 'duration' Value = datetime.timedelta NATIVE_MAP.update({ datetime.datetime: DateTime, datetime.time: Time, datetime.date: Date, datetime.timedelta: Duration, }) spyne-spyne-2.14.0/spyne/model/primitive/network.py000066400000000000000000000121261417664205300224130ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.model._base import SimpleModel from spyne.model.primitive._base import re_match_with_span from spyne.model.primitive.string import Unicode _PATT_MAC = "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})" def _validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and (value is None or ( cls.Attributes.min_len <= len(value) <= cls.Attributes.max_len and re_match_with_span(cls.Attributes, value) ))) _mac_validate = { None: _validate_string, # TODO: add int serialization } _MacBase = Unicode(max_len=17, min_len=17, pattern=_PATT_MAC) class MacAddress(_MacBase): """Unicode subclass for a MAC address.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_mac' class Attributes(_MacBase.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _mac_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) _PATT_IPV4_FRAG = r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" _PATT_IPV4 = r"(%(P4)s\.){3,3}%(P4)s" % {'P4': _PATT_IPV4_FRAG} _ipv4_validate = { None: _validate_string, # TODO: add int serialization } _Ipv4Base = Unicode(15, pattern=_PATT_IPV4) class Ipv4Address(_Ipv4Base): """Unicode subclass for an IPv4 address.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ipv4' class Attributes(_Ipv4Base.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ipv4_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) # http://stackoverflow.com/a/1934546 _PATT_IPV6_FRAG = "[0-9a-fA-F]{1,4}" _PATT_IPV6 = ("(" "(%(P6)s:){7,7}%(P6)s|" # 1:2:3:4:5:6:7:8 "(%(P6)s:){1,7}:|" # 1:: 1:2:3:4:5:6:7:: "(%(P6)s:){1,6}:%(P6)s|" # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 "(%(P6)s:){1,5}(:%(P6)s){1,2}|" # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 "(%(P6)s:){1,4}(:%(P6)s){1,3}|" # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 "(%(P6)s:){1,3}(:%(P6)s){1,4}|" # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 "(%(P6)s:){1,2}(:%(P6)s){1,5}|" # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 "%(P6)s:((:%(P6)s){1,6})|" # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 ":((:%(P6)s){1,7}|:)|" # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: "fe80:(:%(P6)s){0,4}%%[0-9a-zA-Z]{1,}|" # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) "::(ffff(:0{1,4}){0,1}:){0,1}%(A4)s|" # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) "(%(P6)s:){1,4}:%(A4)s" # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) ")") % {'P6': _PATT_IPV6_FRAG, 'A4': _PATT_IPV4} _ipv6_validate = { None: _validate_string, # TODO: add int serialization } _Ipv6Base = Unicode(45, pattern=_PATT_IPV6) class Ipv6Address(_Ipv6Base): """Unicode subclass for an IPv6 address.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ipv6' class Attributes(_Ipv6Base.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ipv6_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) _PATT_IPV4V6 = "(%s|%s)" % (_PATT_IPV4, _PATT_IPV6) _ip_validate = { None: _validate_string, # TODO: add int serialization } _IpAddressBase = Unicode(45, pattern=_PATT_IPV4V6) class IpAddress(_IpAddressBase): """Unicode subclass for an IPv4 or IPv6 address.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'addr_ip' class Attributes(_IpAddressBase.Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _ip_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) spyne-spyne-2.14.0/spyne/model/primitive/number.py000066400000000000000000000336111417664205300222140ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import math import decimal import platform from _warnings import warn from spyne.model import SimpleModel from spyne.model.primitive import NATIVE_MAP from spyne.util import six class NumberLimitsWarning(Warning): pass class Decimal(SimpleModel): """The primitive that corresponds to the native python Decimal. This is also the base class for denoting numbers. Note that it is your responsibility to make sure that the scale and precision constraints set in this type is consistent with the values in the context of the decimal package. See the :func:`decimal.getcontext` documentation for more information. """ __type_name__ = 'decimal' Value = decimal.Decimal # contrary to popular belief, Decimal hates float. class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Decimal` type.""" gt = decimal.Decimal('-inf') # minExclusive """The value should be greater than this number.""" ge = decimal.Decimal('-inf') # minInclusive """The value should be greater than or equal to this number.""" lt = decimal.Decimal('inf') # maxExclusive """The value should be lower than this number.""" le = decimal.Decimal('inf') # maxInclusive """The value should be lower than or equal to this number.""" max_str_len = 1024 """The maximum length of string to be attempted to convert to number.""" format = None """A regular python string formatting string. See here: http://docs.python.org/2/library/stdtypes.html#string-formatting""" str_format = None """A regular python string formatting string used by invoking its ``format()`` function. See here: http://docs.python.org/2/library/string.html#format-string-syntax""" pattern = None """A regular expression that matches the whole field. See here for more info: http://www.regular-expressions.info/xml.html""" total_digits = decimal.Decimal('inf') """Maximum number of digits.""" fraction_digits = decimal.Decimal('inf') """Maximum number of digits after the decimal separator.""" min_bound = None """Hardware limit that determines the lowest value this type can store.""" max_bound = None """Hardware limit that determines the highest value this type can store.""" def __new__(cls, *args, **kwargs): assert len(args) <= 2 if len(args) >= 1 and args[0] is not None: kwargs['total_digits'] = args[0] kwargs['fraction_digits'] = 0 if len(args) == 2 and args[1] is not None: kwargs['fraction_digits'] = args[1] retval = SimpleModel.__new__(cls, **kwargs) return retval @classmethod def _s_customize(cls, **kwargs): td = kwargs.get('total_digits', None) fd = kwargs.get('fraction_digits', None) if td is not None and fd is not None: assert td > 0, "'total_digits' must be positive." assert fd <= td, \ "'total_digits' must be greater than" \ " or equal to 'fraction_digits'." \ " %r ! <= %r" % (fd, td) msl = kwargs.get('max_str_len', None) if msl is None: kwargs['max_str_len'] = cls.Attributes.total_digits + 2 # + 1 for decimal separator # + 1 for negative sign else: kwargs['max_str_len'] = msl minb = cls.Attributes.min_bound maxb = cls.Attributes.max_bound ge = kwargs.get("ge", None) gt = kwargs.get("gt", None) le = kwargs.get("le", None) lt = kwargs.get("lt", None) if minb is not None: if ge is not None and ge < minb: warn("'Greater than or equal value' %d smaller than min_bound %d" % (ge, minb), NumberLimitsWarning) if gt is not None and gt < minb: warn("'Greater than' value %d smaller than min_bound %d" % (gt, minb), NumberLimitsWarning) if le is not None and le < minb: raise ValueError( "'Little than or equal' value %d smaller than min_bound %d" % (le, minb)) if lt is not None and lt <= minb: raise ValueError( "'Little than' value %d smaller than min_bound %d" % (lt, minb)) if maxb is not None: if le is not None and le > maxb: warn("'Little than or equal' value %d greater than max_bound %d" % (le, maxb), NumberLimitsWarning) if lt is not None and lt > maxb: warn("'Little than' value %d greater than max_bound %d" % (lt, maxb), NumberLimitsWarning) if ge is not None and ge > maxb: raise ValueError( "'Greater than or equal' value %d greater than max_bound %d" % (ge, maxb)) if gt is not None and gt >= maxb: raise ValueError( "'Greater than' value %d greater than max_bound %d" % (gt, maxb)) return super(Decimal, cls)._s_customize(**kwargs) @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Decimal.Attributes.gt and cls.Attributes.ge == Decimal.Attributes.ge and cls.Attributes.lt == Decimal.Attributes.lt and cls.Attributes.le == Decimal.Attributes.le and cls.Attributes.total_digits == Decimal.Attributes.total_digits and cls.Attributes.fraction_digits == Decimal.Attributes.fraction_digits ) @staticmethod def validate_string(cls, value): return SimpleModel.validate_string(cls, value) and ( value is None or (len(value) <= cls.Attributes.max_str_len) ) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) and ( value is None or ( value > cls.Attributes.gt and value >= cls.Attributes.ge and value < cls.Attributes.lt and value <= cls.Attributes.le )) class Double(Decimal): """As this type is serialized as the python ``float`` type, it comes with its gotchas. Unless you know what you're doing, you should use a :class:`Decimal` with a pre-defined number of integer and decimal digits because the string representation of a floating-point number translates better to or from the Decimal type. .. NOTE:: This class is not compatible with :class:`spyne.model.Decimal`. You can get strange results if you're using a `decimal.Decimal` instance for a field denoted as `Double` or `Float` and vice versa. Make sure you only return instances of types compatible with designated types. """ __type_name__ = 'double' Value = float if platform.python_version_tuple()[:2] == ('2','6'): class Attributes(Decimal.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Double` type. This class is only here for Python 2.6: See this bug report for more info: http://bugs.python.org/issue2531 """ gt = float('-inf') # minExclusive """The value should be greater than this number.""" ge = float('-inf') # minInclusive """The value should be greater than or equal to this number.""" lt = float('inf') # maxExclusive """The value should be lower than this number.""" le = float('inf') # maxInclusive """The value should be lower than or equal to this number.""" @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.gt == Double.Attributes.gt and cls.Attributes.ge == Double.Attributes.ge and cls.Attributes.lt == Double.Attributes.lt and cls.Attributes.le == Double.Attributes.le ) class Float(Double): """Synonym for Double (as far as python side of things are concerned). It's here for compatibility reasons.""" __type_name__ = 'float' class Integer(Decimal): """The arbitrary-size signed integer.""" __type_name__ = 'integer' Value = int @staticmethod def validate_native(cls, value): return ( Decimal.validate_native(cls, value) and (value is None or int(value) == value) ) class UnsignedInteger(Integer): """The arbitrary-size unsigned integer, also known as nonNegativeInteger.""" __type_name__ = 'nonNegativeInteger' @staticmethod def validate_native(cls, value): return ( Integer.validate_native(cls, value) and (value is None or value >= 0) ) NonNegativeInteger = UnsignedInteger """The arbitrary-size unsigned integer, alias for UnsignedInteger.""" class PositiveInteger(NonNegativeInteger): """The arbitrary-size positive integer (natural number).""" __type_name__ = 'positiveInteger' @staticmethod def validate_native(cls, value): return (Integer.validate_native(cls, value) and (value is None or value > 0)) def TBoundedInteger(num_bits, type_name): _min_b = -(0x8<<(num_bits-4)) # 0x8 is 4 bits. _max_b = (0x8<<(num_bits-4)) - 1 # -1? c'est la vie class _BoundedInteger(Integer): __type_name__ = type_name class Attributes(Integer.Attributes): max_str_len = math.ceil(math.log(2**num_bits, 10)) min_bound = _min_b max_bound = _max_b @staticmethod def validate_native(cls, value): return ( Integer.validate_native(cls, value) and (value is None or (_min_b <= value <= _max_b)) ) return _BoundedInteger def TBoundedUnsignedInteger(num_bits, type_name): _min_b = 0 _max_b = 2 ** num_bits - 1 # -1? c'est la vie ;) class _BoundedUnsignedInteger(UnsignedInteger): __type_name__ = type_name class Attributes(UnsignedInteger.Attributes): max_str_len = math.ceil(math.log(2**num_bits, 10)) min_bound = _min_b max_bound = _max_b @staticmethod def validate_native(cls, value): return ( UnsignedInteger.validate_native(cls, value) and (value is None or (_min_b <= value < _max_b)) ) return _BoundedUnsignedInteger Integer64 = TBoundedInteger(64, 'long') """The 64-bit signed integer, also known as ``long``.""" Long = Integer64 """The 64-bit signed integer, alias for :class:`Integer64`.""" Integer32 = TBoundedInteger(32, 'int') """The 64-bit signed integer, also known as ``int``.""" Int = Integer32 """The 32-bit signed integer, alias for :class:`Integer32`.""" Integer16 = TBoundedInteger(16, 'short') """The 16-bit signed integer, also known as ``short``.""" Short = Integer16 """The 16-bit signed integer, alias for :class:`Integer16`.""" Integer8 = TBoundedInteger(8, 'byte') """The 8-bit signed integer, also known as ``byte``.""" Byte = Integer8 """The 8-bit signed integer, alias for :class:`Integer8`.""" UnsignedInteger64 = TBoundedUnsignedInteger(64, 'unsignedLong') """The 64-bit unsigned integer, also known as ``unsignedLong``.""" UnsignedLong = UnsignedInteger64 """The 64-bit unsigned integer, alias for :class:`UnsignedInteger64`.""" UnsignedInteger32 = TBoundedUnsignedInteger(32, 'unsignedInt') """The 64-bit unsigned integer, also known as ``unsignedInt``.""" UnsignedInt = UnsignedInteger32 """The 32-bit unsigned integer, alias for :class:`UnsignedInteger32`.""" UnsignedInteger16 = TBoundedUnsignedInteger(16, 'unsignedShort') """The 16-bit unsigned integer, also known as ``unsignedShort``.""" UnsignedShort = UnsignedInteger16 """The 16-bit unsigned integer, alias for :class:`UnsignedInteger16`.""" UnsignedInteger8 = TBoundedUnsignedInteger(8, 'unsignedByte') """The 8-bit unsigned integer, also known as ``unsignedByte``.""" UnsignedByte = UnsignedInteger8 """The 8-bit unsigned integer, alias for :class:`UnsignedInteger8`.""" NATIVE_MAP.update({ float: Double, decimal.Decimal: Decimal, }) if not six.PY2: NATIVE_MAP.update({ int: Integer, }) else: NATIVE_MAP.update({ long: Integer, }) if isinstance(0x80000000, long): # 32-bit architecture NATIVE_MAP[int] = Integer32 else: # not 32-bit (so most probably 64-bit) architecture NATIVE_MAP[int] = Integer64 spyne-spyne-2.14.0/spyne/model/primitive/spatial.py000066400000000000000000000177701417664205300223710ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # # FIXME: Supports e.g. # MULTIPOINT (10 40, 40 30, 20 20, 30 10) # # but not: # MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) # from spyne.model import SimpleModel from spyne.model.primitive.string import Unicode FLOAT_PATTERN = r'-?[0-9]+\.?[0-9]*(e-?[0-9]+)?' _rinse_and_repeat = r'\s*\(%s\s*(,\s*%s)*\)\s*' def _get_one_point_pattern(dim): return ' +'.join([FLOAT_PATTERN] * dim) def _get_point_pattern(dim): return r'POINT\s*\(%s\)' % _get_one_point_pattern(dim) def _get_one_multipoint_pattern(dim): one_point = _get_one_point_pattern(dim) return _rinse_and_repeat % (one_point, one_point) def _get_multipoint_pattern(dim): return r'MULTIPOINT%s' % _get_one_multipoint_pattern(dim) def _get_one_line_pattern(dim): one_point = _get_one_point_pattern(dim) return _rinse_and_repeat % (one_point, one_point) def _get_linestring_pattern(dim): return r'LINESTRING%s' % _get_one_line_pattern(dim) def _get_one_multilinestring_pattern(dim): one_line = _get_one_line_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_multilinestring_pattern(dim): return r'MULTILINESTRING%s' % _get_one_multilinestring_pattern(dim) def _get_one_polygon_pattern(dim): one_line = _get_one_line_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_polygon_pattern(dim): return r'POLYGON%s' % _get_one_polygon_pattern(dim) def _get_one_multipolygon_pattern(dim): one_line = _get_one_polygon_pattern(dim) return _rinse_and_repeat % (one_line, one_line) def _get_multipolygon_pattern(dim): return r'MULTIPOLYGON%s' % _get_one_multipolygon_pattern(dim) class Point(Unicode): """A point type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper point type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None @staticmethod def Value(x, y, prec=15): if isinstance(x, str) or isinstance(y, str): assert isinstance(x, str) assert isinstance(y, str) return 'POINT(%s %s)' % (x, y) return ('POINT(%%3.%(prec)sf %%3.%(prec)sf)' % {'prec': prec}) % (x,y) def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_point_pattern(dim) kwargs['type_name'] = 'point%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class Line(Unicode): """A line type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper line type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_linestring_pattern(dim) kwargs['type_name'] = 'line%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval LineString = Line class Polygon(Unicode): """A polygon type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper polygon type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_polygon_pattern(dim) kwargs['type_name'] = 'polygon%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class MultiPoint(Unicode): """A MultiPoint type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiPoint type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multipoint_pattern(dim) kwargs['type_name'] = 'multiPoint%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval class MultiLine(Unicode): """A MultiLine type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiLine type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multilinestring_pattern(dim) kwargs['type_name'] = 'multiLine%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval MultiLineString = MultiLine class MultiPolygon(Unicode): """A MultiPolygon type whose native format is a WKT string. You can use :func:`shapely.wkt.loads` to get a proper MultiPolygon type. It's a subclass of the :class:`Unicode` type, so regular Unicode constraints apply. The only additional parameter is the number of dimensions. :param dim: Number of dimensons. """ __type_name__ = None class Attributes(Unicode.Attributes): dim = None def __new__(cls, dim=None, **kwargs): assert dim in (None, 2, 3) if dim is not None: kwargs['dim'] = dim kwargs['pattern'] = _get_multipolygon_pattern(dim) kwargs['type_name'] = 'multipolygon%dd' % dim retval = SimpleModel.__new__(cls, **kwargs) retval.__namespace__ = 'http://spyne.io/schema' retval.__extends__ = Unicode retval.__orig__ = Unicode return retval spyne-spyne-2.14.0/spyne/model/primitive/string.py000066400000000000000000000231261417664205300222320ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import unicode_literals import decimal import uuid from spyne.model.primitive import NATIVE_MAP from spyne.util import six from spyne.model._base import SimpleModel from spyne.model.primitive._base import re_match_with_span UUID_PATTERN = "%(x)s{8}-%(x)s{4}-%(x)s{4}-%(x)s{4}-%(x)s{12}" % \ {'x': '[a-fA-F0-9]'} LTREE_PATTERN = r"\w+(\.\w+)*" # Actual ltree max size is 65536 but it's advised to keep it under 2048. LTREE_OPTIMAL_SIZE = 2048 LTREE_MAXIMUM_SIZE = 65536 '[0-9A-Za-z!#$%&\'*+.^_`|~-]+/([0-9A-Za-z!#$%&\'*+.^_`|~-]+);[ \\t]*[0-9A-Za-z!#$%&\'*+.^_`|~-]+=(?:[0-9A-Za-z!#$%&\'*+.^_`|~-]+|"(?:[^"\\\\]|\\.)*");?[ \\t]*([0-9A-Za-z!#$%&\'*+.^_`|~-]+=(?:[0-9A-Za-z!#$%&\'*+.^_`|~-]+|"(?:[^"\\\\]|\\.)*");?[ \\t]*)*' def _gen_mime_type_pattern(strict, with_params): ows = "[ \\t]*" # Optional WhiteSpace token = "[0-9A-Za-z!#$%&'*+.^_`|~-]+" quotedString = "\"(?:[^\"\\\\]|\\.)*\"" if strict: main_type = "(" \ "application|audio|font|example|image|message|model|multipart" \ "|text|video|x-(?:" + token + ")" \ ")" else: main_type = token if not with_params: return main_type + "/" + "(" + token + ")" param = token + "=" + "(?:" + token + "|" + quotedString + ");?" + ows params = \ "(" + ";" \ + "(" + ows + param + "(" + param + ")*" + ")?" \ + ")?" return main_type + "/" + "(" + token + ")" + params MIME_TYPE_PATTERN_STRICT = \ _gen_mime_type_pattern(strict=True, with_params=False) MIME_TYPE_PATTERN_PERMISSIVE = \ _gen_mime_type_pattern(strict=False, with_params=False) MEDIA_TYPE_PATTERN_STRICT = \ _gen_mime_type_pattern(strict=True, with_params=True) MEDIA_TYPE_PATTERN_PERMISSIVE = \ _gen_mime_type_pattern(strict=False, with_params=True) class Unicode(SimpleModel): """The type to represent human-readable data. Its native format is `unicode` or `str` with given encoding. """ __type_name__ = 'string' Value = six.text_type class Attributes(SimpleModel.Attributes): """Customizable attributes of the :class:`spyne.model.primitive.Unicode` type.""" min_len = 0 """Minimum length of string. Can be set to any positive integer""" max_len = decimal.Decimal('inf') """Maximum length of string. Can be set to ``decimal.Decimal('inf')`` to accept strings of arbitrary length. You may also need to adjust :const:`spyne.server.wsgi.MAX_CONTENT_LENGTH`.""" pattern = None """A regular expression that matches the whole string. See here for more info: http://www.regular-expressions.info/xml.html""" unicode_pattern = None """Same as ``pattern``, but, will be compiled with ``re.UNICODE``. See: https://docs.python.org/2/library/re.html#re.UNICODE""" encoding = None """The encoding of binary data this class may have to deal with.""" unicode_errors = 'strict' """The argument to the ``unicode`` builtin; one of 'strict', 'replace' or 'ignore'.""" format = None """A regular python string formatting string. See here: http://docs.python.org/library/stdtypes.html#string-formatting""" cast = None """Type override callable for casting non-unicode input to unicode.""" def __new__(cls, *args, **kwargs): assert len(args) <= 1 if len(args) == 1: kwargs['max_len'] = args[0] retval = SimpleModel.__new__(cls, ** kwargs) return retval @staticmethod def is_default(cls): return ( SimpleModel.is_default(cls) and cls.Attributes.min_len == Unicode.Attributes.min_len and cls.Attributes.max_len == Unicode.Attributes.max_len and cls.Attributes.pattern == Unicode.Attributes.pattern ) @staticmethod def validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and (value is None or ( cls.Attributes.min_len <= len(value) <= cls.Attributes.max_len ))) @staticmethod def validate_native(cls, value): return (SimpleModel.validate_native(cls, value) and (value is None or ( re_match_with_span(cls.Attributes, value) ))) class String(Unicode): pass if not six.PY2: String = Unicode class AnyUri(Unicode): """A special kind of String type designed to hold an uri.""" __type_name__ = 'anyURI' class Attributes(String.Attributes): text = None """The text shown in link.""" anchor_class = None """The class of the generated tag.""" class Value(object): """A special object that is just a better way of carrying the information carried with a link. :param href: The uri string. :param text: The text data that goes with the link. This is a ``str`` or a ``unicode`` instance. :param content: The structured data that goes with the link. This is an `lxml.etree.Element` instance. """ def __init__(self, href, text=None, content=None): self.href = href self.text = text self.content = content def __repr__(self): return "Uri(href={0!r}, text={1!r}, content={2!r})" \ .format(self.href, self.text, self.content) class ImageUri(AnyUri): """A special kind of String that holds the uri of an image.""" def _uuid_validate_string(cls, value): return ( SimpleModel.validate_string(cls, value) and (value is None or ( cls.Attributes.min_len <= len(value) <= cls.Attributes.max_len and re_match_with_span(cls.Attributes, value) ))) def _Tuuid_validate(key): from uuid import UUID def _uvalid(cls, v): try: UUID(**{key:v}) except ValueError: return False return True return _uvalid _uuid_validate = { None: _uuid_validate_string, 'hex': _Tuuid_validate('hex'), 'urn': _Tuuid_validate('urn'), six.binary_type: _Tuuid_validate('bytes'), 'bytes': _Tuuid_validate('bytes'), 'bytes_le': _Tuuid_validate('bytes_le'), 'fields': _Tuuid_validate('fields'), int: _Tuuid_validate('int'), 'int': _Tuuid_validate('int'), } class Uuid(Unicode(pattern=UUID_PATTERN)): """Unicode subclass for Universially-Unique Identifiers.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'uuid' Value = uuid.UUID class Attributes(Unicode(pattern=UUID_PATTERN).Attributes): serialize_as = None @staticmethod def validate_string(cls, value): return _uuid_validate[cls.Attributes.serialize_as](cls, value) @staticmethod def validate_native(cls, value): return SimpleModel.validate_native(cls, value) class Ltree(Unicode(LTREE_OPTIMAL_SIZE, unicode_pattern=LTREE_PATTERN)): """A special kind of String type designed to hold the Ltree type from Postgresql.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'ltreeString' class LtreeLarge(Unicode(LTREE_MAXIMUM_SIZE, unicode_pattern=LTREE_PATTERN)): """A special kind of String type designed to hold the Ltree type from Postgresql.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'largeLtreeString' class MimeTypeStrict(Unicode(unicode_pattern=MIME_TYPE_PATTERN_STRICT)): """A special kind of String type designed to hold a mime type as defined by IANA.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'strictMimeTypeString' class MimeType(Unicode(unicode_pattern=MIME_TYPE_PATTERN_PERMISSIVE)): """A special kind of String type designed to hold a forward-compatible mime type that can have any string as main type.""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'mimeTypeString' class MediaTypeStrict(Unicode(unicode_pattern=MEDIA_TYPE_PATTERN_STRICT)): """A special kind of String type designed to hold a mime type as defined by IANA followed by arbitrary parameters. See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'strictMediaTypeString' class MediaType(Unicode(unicode_pattern=MEDIA_TYPE_PATTERN_PERMISSIVE)): """A special kind of String type designed to hold a forward-compatible media type that can have any string as main type. A media type is essentially a mime type plus parameters. See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1""" __namespace__ = 'http://spyne.io/schema' __type_name__ = 'mediaTypeString' if not six.PY2: NATIVE_MAP.update({ str: Unicode, }) else: NATIVE_MAP.update({ str: String, unicode: Unicode, }) spyne-spyne-2.14.0/spyne/model/primitive/xml.py000066400000000000000000000204371417664205300215260ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import re from spyne.const.xml import PATT_NMTOKEN from spyne.model.primitive.string import Unicode RE_BaseChar = re.compile( u"[\u0041-\u005A]|[\u0061-\u007A]|[\u00C0-\u00D6]|[\u00D8-\u00F6]|" u"[\u00F8-\u00FF]|[\u0100-\u0131]|[\u0134-\u013E]|[\u0141-\u0148]|" u"[\u014A-\u017E]|[\u0180-\u01C3]|[\u01CD-\u01F0]|[\u01F4-\u01F5]|" u"[\u01FA-\u0217]|[\u0250-\u02A8]|[\u02BB-\u02C1]|\u0386|[\u0388-\u038A]|" u"\u038C|[\u038E-\u03A1]|[\u03A3-\u03CE]|[\u03D0-\u03D6]|" u"\u03DA|\u03DC|\u03DE|\u03E0|[\u03E2-\u03F3]|[\u0401-\u040C]|" u"[\u040E-\u044F]|[\u0451-\u045C]|[\u045E-\u0481]|[\u0490-\u04C4]|" u"[\u04C7-\u04C8]|[\u04CB-\u04CC]|[\u04D0-\u04EB]|[\u04EE-\u04F5]|" u"[\u04F8-\u04F9]|[\u0531-\u0556]|\u0559|[\u0561-\u0586]|[\u05D0-\u05EA]|" u"[\u05F0-\u05F2]|[\u0621-\u063A]|[\u0641-\u064A]|[\u0671-\u06B7]|" u"[\u06BA-\u06BE]|[\u06C0-\u06CE]|[\u06D0-\u06D3]|\u06D5|[\u06E5-\u06E6]|" u"[\u0905-\u0939]|\u093D|[\u0958-\u0961]|[\u0985-\u098C]|[\u098F-\u0990]|" u"[\u0993-\u09A8]|[\u09AA-\u09B0]|\u09B2|[\u09B6-\u09B9]|[\u09DC-\u09DD]|" u"[\u09DF-\u09E1]|[\u09F0-\u09F1]|[\u0A05-\u0A0A]|[\u0A0F-\u0A10]|" u"[\u0A13-\u0A28]|[\u0A2A-\u0A30]|[\u0A32-\u0A33]|[\u0A35-\u0A36]|" u"[\u0A38-\u0A39]|[\u0A59-\u0A5C]|\u0A5E|[\u0A72-\u0A74]|[\u0A85-\u0A8B]|" u"\u0A8D|[\u0A8F-\u0A91]|[\u0A93-\u0AA8]|[\u0AAA-\u0AB0]|[\u0AB2-\u0AB3]|" u"[\u0AB5-\u0AB9]|\u0ABD|\u0AE0|[\u0B05-\u0B0C]|[\u0B0F-\u0B10]|" u"[\u0B13-\u0B28]|[\u0B2A-\u0B30]|[\u0B32-\u0B33]|[\u0B36-\u0B39]|\u0B3D|" u"[\u0B5C-\u0B5D]|[\u0B5F-\u0B61]|[\u0B85-\u0B8A]|[\u0B8E-\u0B90]|" u"[\u0B92-\u0B95]|[\u0B99-\u0B9A]|\u0B9C|[\u0B9E-\u0B9F]|[\u0BA3-\u0BA4]|" u"[\u0BA8-\u0BAA]|[\u0BAE-\u0BB5]|[\u0BB7-\u0BB9]|[\u0C05-\u0C0C]|" u"[\u0C0E-\u0C10]|[\u0C12-\u0C28]|[\u0C2A-\u0C33]|[\u0C35-\u0C39]|" u"[\u0C60-\u0C61]|[\u0C85-\u0C8C]|[\u0C8E-\u0C90]|[\u0C92-\u0CA8]|" u"[\u0CAA-\u0CB3]|[\u0CB5-\u0CB9]|\u0CDE|[\u0CE0-\u0CE1]|[\u0D05-\u0D0C]|" u"[\u0D0E-\u0D10]|[\u0D12-\u0D28]|[\u0D2A-\u0D39]|[\u0D60-\u0D61]|" u"[\u0E01-\u0E2E]|\u0E30|[\u0E32-\u0E33]|[\u0E40-\u0E45]|[\u0E81-\u0E82]|" u"\u0E84|[\u0E87-\u0E88]|\u0E8A|\u0E8D|[\u0E94-\u0E97]|[\u0E99-\u0E9F]|" u"[\u0EA1-\u0EA3]|\u0EA5|\u0EA7|[\u0EAA-\u0EAB]|[\u0EAD-\u0EAE]|\u0EB0|" u"[\u0EB2-\u0EB3]|\u0EBD|[\u0EC0-\u0EC4]|[\u0F40-\u0F47]|[\u0F49-\u0F69]|" u"[\u10A0-\u10C5]|[\u10D0-\u10F6]|\u1100|[\u1102-\u1103]|[\u1105-\u1107]|" u"\u1109|[\u110B-\u110C]|[\u110E-\u1112]|\u113C|\u113E|\u1140|\u114C|" u"\u114E|\u1150|[\u1154-\u1155]|\u1159|[\u115F-\u1161]|\u1163|\u1165|" u"\u1167|\u1169|[\u116D-\u116E]|[\u1172-\u1173]|\u1175|\u119E|\u11A8|" u"\u11AB|[\u11AE-\u11AF]|[\u11B7-\u11B8]|\u11BA|[\u11BC-\u11C2]|\u11EB|" u"\u11F0|\u11F9|[\u1E00-\u1E9B]|[\u1EA0-\u1EF9]|[\u1F00-\u1F15]|" u"[\u1F18-\u1F1D]|[\u1F20-\u1F45]|[\u1F48-\u1F4D]|[\u1F50-\u1F57]|\u1F59|" u"\u1F5B|\u1F5D|[\u1F5F-\u1F7D]|[\u1F80-\u1FB4]|[\u1FB6-\u1FBC]|\u1FBE|" u"[\u1FC2-\u1FC4]|[\u1FC6-\u1FCC]|[\u1FD0-\u1FD3]|[\u1FD6-\u1FDB]|" u"[\u1FE0-\u1FEC]|[\u1FF2-\u1FF4]|[\u1FF6-\u1FFC]|\u2126|[\u212A-\u212B]|" u"\u212E|[\u2180-\u2182]|[\u3041-\u3094]|[\u30A1-\u30FA]|[\u3105-\u312C]|" u"[\uAC00-\uD7A3]", flags=re.UNICODE) RE_Ideographic = re.compile(u"[\u4E00-\u9FA5]|\u3007|[\u3021-\u3029]", flags=re.UNICODE) RE_CombiningChar= re.compile( u"[\u0300-\u0345]|[\u0360-\u0361]|[\u0483-\u0486]|[\u0591-\u05A1]|" u"[\u05A3-\u05B9]|[\u05BB-\u05BD]|\u05BF|[\u05C1-\u05C2]|\u05C4|" u"[\u064B-\u0652]|\u0670|[\u06D6-\u06DC]|[\u06DD-\u06DF]|[\u06E0-\u06E4]|" u"[\u06E7-\u06E8]|[\u06EA-\u06ED]|[\u0901-\u0903]|\u093C|[\u093E-\u094C]|" u"\u094D|[\u0951-\u0954]|[\u0962-\u0963]|[\u0981-\u0983]|\u09BC|\u09BE|" u"\u09BF|[\u09C0-\u09C4]|[\u09C7-\u09C8]|[\u09CB-\u09CD]|\u09D7|" u"[\u09E2-\u09E3]|\u0A02|\u0A3C|\u0A3E|\u0A3F|[\u0A40-\u0A42]|" u"[\u0A47-\u0A48]|[\u0A4B-\u0A4D]|[\u0A70-\u0A71]|[\u0A81-\u0A83]|\u0ABC|" u"[\u0ABE-\u0AC5]|[\u0AC7-\u0AC9]|[\u0ACB-\u0ACD]|[\u0B01-\u0B03]|\u0B3C|" u"[\u0B3E-\u0B43]|[\u0B47-\u0B48]|[\u0B4B-\u0B4D]|[\u0B56-\u0B57]|" u"[\u0B82-\u0B83]|[\u0BBE-\u0BC2]|[\u0BC6-\u0BC8]|[\u0BCA-\u0BCD]|\u0BD7|" u"[\u0C01-\u0C03]|[\u0C3E-\u0C44]|[\u0C46-\u0C48]|[\u0C4A-\u0C4D]|" u"[\u0C55-\u0C56]|[\u0C82-\u0C83]|[\u0CBE-\u0CC4]|[\u0CC6-\u0CC8]|" u"[\u0CCA-\u0CCD]|[\u0CD5-\u0CD6]|[\u0D02-\u0D03]|[\u0D3E-\u0D43]|" u"[\u0D46-\u0D48]|[\u0D4A-\u0D4D]|\u0D57|\u0E31|[\u0E34-\u0E3A]|" u"[\u0E47-\u0E4E]|\u0EB1|[\u0EB4-\u0EB9]|[\u0EBB-\u0EBC]|[\u0EC8-\u0ECD]|" u"[\u0F18-\u0F19]|\u0F35|\u0F37|\u0F39|\u0F3E|\u0F3F|[\u0F71-\u0F84]|" u"[\u0F86-\u0F8B]|[\u0F90-\u0F95]|\u0F97|[\u0F99-\u0FAD]|[\u0FB1-\u0FB7]|" u"\u0FB9|[\u20D0-\u20DC]|\u20E1|[\u302A-\u302F]|\u3099|\u309A", flags=re.UNICODE) RE_Digit = re.compile( u"[\u0030-\u0039]|[\u0660-\u0669]|[\u06F0-\u06F9]|[\u0966-\u096F]|" u"[\u09E6-\u09EF]|[\u0A66-\u0A6F]|[\u0AE6-\u0AEF]|[\u0B66-\u0B6F]|" u"[\u0BE7-\u0BEF]|[\u0C66-\u0C6F]|[\u0CE6-\u0CEF]|[\u0D66-\u0D6F]|" u"[\u0E50-\u0E59]|[\u0ED0-\u0ED9]|[\u0F20-\u0F29]", flags=re.UNICODE) RE_Extender = re.compile( u"\u00B7|\u02D0|\u02D1|\u0387|\u0640|\u0E46|\u0EC6|\u3005|[\u3031-\u3035]|" u"[\u309D-\u309E]|[\u30FC-\u30FE]", flags=re.UNICODE) RE_Letter = re.compile(u'|'.join((RE_BaseChar.pattern, RE_Ideographic.pattern)), flags=re.UNICODE) RE_NameChar = re.compile(u'|'.join(( RE_Letter.pattern, RE_Digit.pattern, '.', '-', '_', ':', RE_CombiningChar.pattern, RE_Extender.pattern, )), flags=re.UNICODE) RE_NCNameChar = re.compile(u'|'.join(( RE_Letter.pattern, RE_Digit.pattern, '.', '-', '_', # <= no column RE_CombiningChar.pattern, RE_Extender.pattern, )), flags=re.UNICODE) class NormalizedString(Unicode): __type_name__ = 'normalizedString' __extends__ = Unicode class Attributes(Unicode.Attributes): white_space = "replace" class Token(NormalizedString): __type_name__ = 'token' class Attributes(Unicode.Attributes): white_space = "collapse" # https://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-Name class Name(Token): __type_name__ = 'Name' class Attributes(Unicode.Attributes): # https://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-Name pattern = '(%s)(%s)*' % ( u'|'.join((RE_Letter.pattern, '_', ':')), RE_NameChar.pattern ) # https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName class NCName(Name): __type_name__ = 'NCName' class Attributes(Unicode.Attributes): pattern = "(%s|_)%s*" % (RE_Letter.pattern, RE_NCNameChar.pattern) # https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName class QName(Token): __type_name__ = "QName" class Attributes(Unicode.Attributes): """ QName = (PrefixedName | UnprefixedName) PrefixedName ::= Prefix ':' LocalPart UnprefixedName ::= LocalPart Prefix ::= NCName LocalPart ::= NCName i.e. QName = (NCName:)?NCName """ pattern = "(%s:)?(%s)" % ( NCName.Attributes.pattern, NCName.Attributes.pattern, ) class NMToken(Unicode): __type_name__ = 'NMTOKEN' class Attributes(Unicode.Attributes): unicode_pattern = PATT_NMTOKEN class ID(NCName): __type_name__ = 'ID' class Language(Token): __type_name__ = 'language' class Attributes(Unicode.Attributes): pattern = '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' spyne-spyne-2.14.0/spyne/model/relational.py000066400000000000000000000023751417664205300210510ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.model.complex import ComplexModel from spyne.model.primitive import Unicode class FileData(ComplexModel): _type_info = [ ('name', Unicode), ('type', Unicode), ('path', Unicode), ] @property def data(self): return self._data @data.setter def data(self, data): self._data = data @property def handle(self): return self._handle @handle.setter def handle(self, handle): self._handle = handle spyne-spyne-2.14.0/spyne/protocol/000077500000000000000000000000001417664205300170775ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/__init__.py000066400000000000000000000036231417664205300212140ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol`` package contains the :class:`spyne.protocol.ProtocolBase`` abstract base class. Every protocol implementation is a subclass of ``ProtocolBase``. """ from spyne.protocol._base import ProtocolMixin from spyne.protocol._inbase import InProtocolBase from spyne.protocol._outbase import OutProtocolBase class ProtocolBase(InProtocolBase, OutProtocolBase): def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, binary_encoding=None, string_encoding='utf8'): InProtocolBase.__init__(self, app=app, validator=validator, mime_type=mime_type, ignore_wrappers=ignore_wrappers, binary_encoding=binary_encoding) OutProtocolBase.__init__(self, app=app, mime_type=mime_type, ignore_wrappers=ignore_wrappers, ignore_uncap=ignore_uncap, binary_encoding=binary_encoding) self.default_string_encoding = string_encoding self.ignore_empty_faultactor = True spyne-spyne-2.14.0/spyne/protocol/_base.py000066400000000000000000000324551417664205300205330ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from datetime import datetime from weakref import WeakKeyDictionary from spyne import ProtocolContext, EventManager from spyne.const import DEFAULT_LOCALE from spyne.model import Array from spyne.error import ResourceNotFoundError from spyne.util import DefaultAttrDict from spyne.util.six import string_types _MISSING = type("_MISSING", (object,), {})() class ProtocolMixin(object): mime_type = 'application/octet-stream' SOFT_VALIDATION = type("Soft", (object,), {}) REQUEST = type("Request", (object,), {}) RESPONSE = type("Response", (object,), {}) type = set() """Set that contains keywords about a protocol.""" default_binary_encoding = None """Default encoding for binary data. It could be e.g. base64.""" default_string_encoding = None """Default encoding for text content. It could be e.g. UTF-8.""" type_attrs = {} """Default customizations to be passed to underlying classes.""" def __init__(self, app=None, mime_type=None, ignore_wrappers=None, binary_encoding=None, string_encoding=None): self.__app = None self.set_app(app) self.ignore_wrappers = ignore_wrappers self.event_manager = EventManager(self) self.binary_encoding = binary_encoding if self.binary_encoding is None: self.binary_encoding = self.default_binary_encoding self.string_encoding = string_encoding if self.string_encoding is None: self.string_encoding = self.default_string_encoding if mime_type is not None: self.mime_type = mime_type self._attrcache = WeakKeyDictionary() self._sortcache = WeakKeyDictionary() def _cast(self, cls_attrs, inst): if cls_attrs.parser is not None: return cls_attrs.parser(inst) return inst _parse = _cast def _sanitize(self, cls_attrs, inst): if cls_attrs.sanitizer is not None: return cls_attrs.sanitizer(inst) return inst def _datetime_from_sec(self, cls, value): try: return datetime.fromtimestamp(value) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_sec_float(self, cls, value): try: return datetime.fromtimestamp(value) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_msec(self, cls, value): try: return datetime.fromtimestamp(value // 1000) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_msec_float(self, cls, value): try: return datetime.fromtimestamp(value / 1000) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_usec(self, cls, value): try: return datetime.fromtimestamp(value / 1e6) except TypeError: logger.error("Invalid value %r", value) raise def _get_datetime_format(self, cls_attrs): # FIXME: this should be dt_format, all other aliases are to be # deprecated dt_format = cls_attrs.datetime_format if dt_format is None: dt_format = cls_attrs.dt_format if dt_format is None: dt_format = cls_attrs.date_format if dt_format is None: dt_format = cls_attrs.out_format if dt_format is None: dt_format = cls_attrs.format return dt_format def _get_date_format(self, cls_attrs): date_format = cls_attrs.date_format if date_format is None: date_format = cls_attrs.format return date_format def _get_time_format(self, cls_attrs): time_format = cls_attrs.time_format if time_format is None: time_format = cls_attrs.format return time_format @property def app(self): return self.__app @staticmethod def strip_wrappers(cls, inst): ti = getattr(cls, '_type_info', {}) while len(ti) == 1 and cls.Attributes._wrapper: # Wrappers are auto-generated objects that have exactly one # child type. key, = ti.keys() if not issubclass(cls, Array): inst = getattr(inst, key, None) cls, = ti.values() ti = getattr(cls, '_type_info', {}) return cls, inst def set_app(self, value): assert self.__app is None, "One protocol instance should belong to one " \ "application instance. It currently belongs " \ "to: %r" % self.__app self.__app = value @staticmethod def issubclass(sub, cls): suborig = getattr(sub, '__orig__', None) clsorig = getattr(cls, '__orig__', None) return issubclass(sub if suborig is None else suborig, cls if clsorig is None else clsorig) def get_cls_attrs(self, cls): logger.debug("%r attrcache size: %d", self, len(self._attrcache)) attr = self._attrcache.get(cls, None) if attr is not None: return attr self._attrcache[cls] = attr = DefaultAttrDict([ (k, getattr(cls.Attributes, k)) for k in dir(cls.Attributes) + META_ATTR if not k.startswith('__')]) if cls.Attributes.prot_attrs: cls_attrs = cls.Attributes.prot_attrs.get(self.__class__, {}) # logger.debug("%r cls attr %r", cls, cls_attrs) attr.update(cls_attrs) inst_attrs = cls.Attributes.prot_attrs.get(self, {}) # logger.debug("%r inst attr %r", cls, cls_attrs) attr.update(inst_attrs) return attr def get_context(self, parent, transport): return ProtocolContext(parent, transport) def generate_method_contexts(self, ctx): """Generates MethodContext instances for every callable assigned to the given method handle. The first element in the returned list is always the primary method context whereas the rest are all auxiliary method contexts. """ call_handles = self.get_call_handles(ctx) if len(call_handles) == 0: raise ResourceNotFoundError(ctx.method_request_string) retval = [] for d in call_handles: assert d is not None c = ctx.copy() c.descriptor = d retval.append(c) return retval def get_call_handles(self, ctx): """Method to be overriden to perform any sort of custom method mapping using any data in the method context. Returns a list of contexts. Can return multiple contexts if a method_request_string matches more than one function. (This is called the fanout mode.) """ name = ctx.method_request_string if not name.startswith(u"{"): name = u'{%s}%s' % (self.app.interface.get_tns(), name) call_handles = self.app.interface.service_method_map.get(name, []) return call_handles def get_polymorphic_target(self, cls, inst): """If the protocol is polymorphic, extract what's returned by the user code. """ if not self.polymorphic: logger.debug("PMORPH Skipped: %r is NOT polymorphic", self) return cls, False orig_cls = cls.__orig__ or cls if inst.__class__ is orig_cls: logger.debug("PMORPH Skipped: Instance class %r is the same as " "designated base class", inst.__class__) return cls, False if not isinstance(inst, orig_cls): logger.debug("PMORPH Skipped: Instance class %r is not a subclass " "of designated base class %r", inst.__class__, orig_cls) return cls, False cls_attr = self.get_cls_attrs(cls) polymap_cls = cls_attr.polymap.get(inst.__class__, None) if polymap_cls is not None: logger.debug("PMORPH OK: cls switch with polymap: %r => %r", cls, polymap_cls) return polymap_cls, True else: logger.debug("PMORPH OK: cls switch without polymap: %r => %r", cls, inst.__class__) return inst.__class__, True @staticmethod def trc_verbose(cls, locale, default): """Translate a class. :param cls: class :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = DEFAULT_LOCALE _log_locale = "default locale '%s'" else: _log_locale = "given locale '%s'" if cls.Attributes.translations is None: retval = default _log_tr = "translated to '%s' without any translations at all with" else: retval = cls.Attributes.translations.get(locale, _MISSING) if retval is _MISSING: retval = default _log_tr = "translated to '%s': No translation for" else: _log_tr = "translated to '%s' with" logger.debug(' '.join(("%r ", _log_tr, _log_locale)), cls, retval, locale) return retval @staticmethod def trc(cls, locale, default): """Translate a class. :param cls: class :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = DEFAULT_LOCALE if cls.Attributes.translations is not None: return cls.Attributes.translations.get(locale, default) return default @staticmethod def trd_verbose(trdict, locale, default): """Translate from a translations dict. :param trdict: translation dict :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = DEFAULT_LOCALE _log_locale = "default locale '%s'" else: _log_locale = "given locale '%s'" if trdict is None: retval = default _log_tr = "translated to '%s' without any translations at all with" elif isinstance(trdict, string_types): retval = trdict _log_tr = "translated to '%s' regardless of" else: retval = trdict.get(locale, _MISSING) if retval is _MISSING: retval = default _log_tr = "translated to '%s': No translation for" else: _log_tr = "translated to '%s' with" logger.debug(' '.join(("%r ", _log_tr, _log_locale)), trdict, retval, locale) return retval @staticmethod def trd(trdict, locale, default): """Translate from a translations dict. :param trdict: translation dict :param locale: locale string :param default: default string if no translation found :returns: translated string """ if locale is None: locale = DEFAULT_LOCALE if trdict is None: return default if isinstance(trdict, string_types): return trdict return trdict.get(locale, default) def sort_fields(self, cls=None, items=None): logger.debug("%r sortcache size: %d", self, len(self._sortcache)) retval = self._sortcache.get(cls, None) if retval is not None: return retval if items is None: items = list(cls.get_flat_type_info(cls).items()) indexes = {} for k, v in items: order = self.get_cls_attrs(v).order if order is not None: if order < 0: indexes[k] = len(items) + order else: indexes[k] = order for k, v in items: order = self.get_cls_attrs(v).order if order is None: indexes[k] = len(indexes) items.sort(key=lambda x: indexes[x[0]]) self._sortcache[cls] = items return items META_ATTR = ['nullable', 'default_factory'] spyne-spyne-2.14.0/spyne/protocol/_inbase.py000066400000000000000000000601541417664205300210570ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) import re import pytz import uuid from math import modf from time import strptime, mktime from datetime import timedelta, time, datetime, date from decimal import Decimal as D, InvalidOperation from pytz import FixedOffset try: from lxml import etree from lxml import html except ImportError: etree = None html = None from spyne.protocol._base import ProtocolMixin from spyne.model import ModelBase, XmlAttribute, Array, Null, \ ByteArray, File, ComplexModelBase, AnyXml, AnyHtml, Unicode, String, \ Decimal, Double, Integer, Time, DateTime, Uuid, Date, Duration, Boolean, Any from spyne.error import ValidationError from spyne.model.binary import binary_decoding_handlers, BINARY_ENCODING_USE_DEFAULT from spyne.util import six from spyne.model.enum import EnumBase from spyne.model.primitive.datetime import TIME_PATTERN, DATE_PATTERN from spyne.util.cdict import cdict _date_re = re.compile(DATE_PATTERN) _time_re = re.compile(TIME_PATTERN) _duration_re = re.compile( r'(?P-?)' r'P' r'(?:(?P\d+)Y)?' r'(?:(?P\d+)M)?' r'(?:(?P\d+)D)?' r'(?:T(?:(?P\d+)H)?' r'(?:(?P\d+)M)?' r'(?:(?P\d+(.\d+)?)S)?)?' ) class InProtocolBase(ProtocolMixin): """This is the abstract base class for all input protocol implementations. Child classes can implement only the required subset of the public methods. An output protocol must implement :func:`serialize` and :func:`create_out_string`. An input protocol must implement :func:`create_in_document`, :func:`decompose_incoming_envelope` and :func:`deserialize`. The ProtocolBase class supports the following events: * ``before_deserialize``: Called before the deserialization operation is attempted. * ``after_deserialize``: Called after the deserialization operation is finished. The arguments the constructor takes are as follows: :param app: The application this protocol belongs to. :param mime_type: The mime_type this protocol should set for transports that support this. This is a quick way to override the mime_type by default instead of subclassing the releavant protocol implementation. """ def __init__(self, app=None, validator=None, mime_type=None, ignore_wrappers=False, binary_encoding=None, string_encoding=None): self.validator = None super(InProtocolBase, self).__init__(app=app, mime_type=mime_type, ignore_wrappers=ignore_wrappers, binary_encoding=binary_encoding, string_encoding=string_encoding) self.message = None self.validator = None self.set_validator(validator) if mime_type is not None: self.mime_type = mime_type fsh = { Any: self.any_from_bytes, Null: self.null_from_bytes, File: self.file_from_bytes, Array: self.array_from_bytes, Double: self.double_from_bytes, String: self.string_from_bytes, AnyXml: self.any_xml_from_bytes, Boolean: self.boolean_from_bytes, Integer: self.integer_from_bytes, Unicode: self.unicode_from_bytes, AnyHtml: self.any_html_from_bytes, ByteArray: self.byte_array_from_bytes, EnumBase: self.enum_base_from_bytes, ModelBase: self.model_base_from_bytes, XmlAttribute: self.xmlattribute_from_bytes, ComplexModelBase: self.complex_model_base_from_bytes } self._from_bytes_handlers = cdict(fsh) self._from_unicode_handlers = cdict(fsh) self._from_bytes_handlers[Date] = self.date_from_bytes self._from_bytes_handlers[Time] = self.time_from_bytes self._from_bytes_handlers[Uuid] = self.uuid_from_bytes self._from_bytes_handlers[Decimal] = self.decimal_from_bytes self._from_bytes_handlers[DateTime] = self.datetime_from_bytes self._from_bytes_handlers[Duration] = self.duration_from_bytes self._from_unicode_handlers[Date] = self.date_from_unicode self._from_unicode_handlers[Uuid] = self.uuid_from_unicode self._from_unicode_handlers[Time] = self.time_from_unicode self._from_unicode_handlers[Decimal] = self.decimal_from_unicode self._from_unicode_handlers[DateTime] = self.datetime_from_unicode self._from_unicode_handlers[Duration] = self.duration_from_unicode self._datetime_dsmap = { None: self._datetime_from_unicode, 'sec': self._datetime_from_sec, 'sec_float': self._datetime_from_sec_float, 'msec': self._datetime_from_msec, 'msec_float': self._datetime_from_msec_float, 'usec': self._datetime_from_usec, } def _datetime_from_sec(self, cls, value): try: return datetime.fromtimestamp(value) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_sec_float(self, cls, value): try: return datetime.fromtimestamp(value) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_msec(self, cls, value): try: return datetime.fromtimestamp(value // 1000) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_msec_float(self, cls, value): try: return datetime.fromtimestamp(value / 1000) except TypeError: logger.error("Invalid value %r", value) raise def _datetime_from_usec(self, cls, value): try: return datetime.fromtimestamp(value / 1e6) except TypeError: logger.error("Invalid value %r", value) raise def create_in_document(self, ctx, in_string_encoding=None): """Uses ``ctx.in_string`` to set ``ctx.in_document``.""" def decompose_incoming_envelope(self, ctx, message): """Sets the ``ctx.method_request_string``, ``ctx.in_body_doc``, ``ctx.in_header_doc`` and ``ctx.service`` properties of the ctx object, if applicable. """ def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE document instance in the ``ctx.in_string`` attribute. Returns the corresponding native python object in the ctx.in_object attribute. """ def validate_document(self, payload): """Method to be overriden to perform any sort of custom input validation on the parsed input document. """ def set_validator(self, validator): """You must override this function if you want your protocol to support validation.""" assert validator is None self.validator = None def from_bytes(self, class_, string, *args, **kwargs): if string is None: return None if isinstance(string, six.string_types) and \ len(string) == 0 and class_.Attributes.empty_is_none: return None handler = self._from_bytes_handlers[class_] return handler(class_, string, *args, **kwargs) def from_unicode(self, class_, string, *args, **kwargs): if string is None: return None #if not six.PY2: # assert isinstance(string, str), \ # "Invalid type passed to `from_unicode`: {}".format( # (class_, type(string), string)) cls_attrs = self.get_cls_attrs(class_) if isinstance(string, six.string_types) and len(string) == 0 and \ cls_attrs.empty_is_none: return None handler = self._from_unicode_handlers[class_] return handler(class_, string, *args, **kwargs) def null_from_bytes(self, cls, value): return None def any_from_bytes(self, cls, value): return value def any_xml_from_bytes(self, cls, string): try: return etree.fromstring(string) except etree.XMLSyntaxError as e: raise ValidationError(string, "%%r: %r" % e) def any_html_from_bytes(self, cls, string): try: return html.fromstring(string) except etree.ParserError as e: if e.args[0] == "Document is empty": pass else: raise def uuid_from_unicode(self, cls, string, suggested_encoding=None): attr = self.get_cls_attrs(cls) ser_as = attr.serialize_as encoding = attr.encoding if encoding is None: encoding = suggested_encoding retval = string if ser_as in ('bytes', 'bytes_le'): retval, = binary_decoding_handlers[encoding](string) try: retval = _uuid_deserialize[ser_as](retval) except (ValueError, TypeError, UnicodeDecodeError) as e: raise ValidationError(e) return retval def uuid_from_bytes(self, cls, string, suggested_encoding=None, **_): attr = self.get_cls_attrs(cls) ser_as = attr.serialize_as encoding = attr.encoding if encoding is None: encoding = suggested_encoding retval = string if ser_as in ('bytes', 'bytes_le'): retval, = binary_decoding_handlers[encoding](string) elif isinstance(string, six.binary_type): retval = string.decode('ascii') try: retval = _uuid_deserialize[ser_as](retval) except ValueError as e: raise ValidationError(e) return retval def unicode_from_bytes(self, cls, value): retval = value if isinstance(value, six.binary_type): cls_attrs = self.get_cls_attrs(cls) if cls_attrs.encoding is not None: retval = six.text_type(value, cls_attrs.encoding, errors=cls_attrs.unicode_errors) elif self.string_encoding is not None: retval = six.text_type(value, self.string_encoding, errors=cls_attrs.unicode_errors) else: retval = six.text_type(value, errors=cls_attrs.unicode_errors) return retval def string_from_bytes(self, cls, value): retval = value cls_attrs = self.get_cls_attrs(cls) if isinstance(value, six.text_type): if cls_attrs.encoding is None: raise Exception("You need to define a source encoding for " "decoding incoming unicode values.") else: retval = value.encode(cls_attrs.encoding) return retval def decimal_from_unicode(self, cls, string): cls_attrs = self.get_cls_attrs(cls) if cls_attrs.max_str_len is not None and len(string) > \ cls_attrs.max_str_len: raise ValidationError(string, "Decimal %%r longer than %d " "characters" % cls_attrs.max_str_len) try: return D(string) except InvalidOperation as e: raise ValidationError(string, "%%r: %r" % e) def decimal_from_bytes(self, cls, string): return self.decimal_from_unicode(cls, string.decode(self.default_string_encoding)) def double_from_bytes(self, cls, string): try: return float(string) except (TypeError, ValueError) as e: raise ValidationError(string, "%%r: %r" % e) def integer_from_bytes(self, cls, string): cls_attrs = self.get_cls_attrs(cls) if isinstance(string, (six.text_type, six.binary_type)) and \ cls_attrs.max_str_len is not None and \ len(string) > cls_attrs.max_str_len: raise ValidationError(string, "Integer %%r longer than %d characters" % cls_attrs.max_str_len) try: return int(string) except ValueError: raise ValidationError(string, "Could not cast %r to integer") def time_from_unicode(self, cls, string): """Expects ISO formatted times.""" match = _time_re.match(string) if match is None: raise ValidationError(string, "%%r does not match regex %r " % _time_re.pattern) fields = match.groupdict(0) microsec = fields.get('sec_frac') if microsec is None or microsec == 0: microsec = 0 else: microsec = min(999999, int(round(float(microsec) * 1e6))) return time(int(fields['hr']), int(fields['min']), int(fields['sec']), microsec) def time_from_bytes(self, cls, string): if isinstance(string, six.binary_type): string = string.decode(self.default_string_encoding) return self.time_from_unicode(cls, string) def date_from_unicode_iso(self, cls, string): """This is used by protocols like SOAP who need ISO8601-formatted dates no matter what. """ try: return date(*(strptime(string, u'%Y-%m-%d')[0:3])) except ValueError: match = cls._offset_re.match(string) if match: year = int(match.group('year')) month = int(match.group('month')) day = int(match.group('day')) return date(year, month, day) raise ValidationError(string) def enum_base_from_bytes(self, cls, value): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, value)): raise ValidationError(value) return getattr(cls, value) def model_base_from_bytes(self, cls, value): return cls.from_bytes(value) def datetime_from_unicode_iso(self, cls, string): astz = self.get_cls_attrs(cls).as_timezone match = cls._utc_re.match(string) if match: tz = pytz.utc retval = _parse_datetime_iso_match(match, tz=tz) if astz is not None: retval = retval.astimezone(astz) return retval if match is None: match = cls._offset_re.match(string) if match: tz_hr, tz_min = [int(match.group(x)) for x in ("tz_hr", "tz_min")] tz = FixedOffset(tz_hr * 60 + tz_min, {}) retval = _parse_datetime_iso_match(match, tz=tz) if astz is not None: retval = retval.astimezone(astz) return retval if match is None: match = cls._local_re.match(string) if match: retval = _parse_datetime_iso_match(match) if astz: retval = retval.replace(tzinfo=astz) return retval raise ValidationError(string) def datetime_from_unicode(self, cls, string): serialize_as = self.get_cls_attrs(cls).serialize_as return self._datetime_dsmap[serialize_as](cls, string) def datetime_from_bytes(self, cls, string): if isinstance(string, six.binary_type): string = string.decode(self.default_string_encoding) serialize_as = self.get_cls_attrs(cls).serialize_as return self._datetime_dsmap[serialize_as](cls, string) def date_from_bytes(self, cls, string): if isinstance(string, six.binary_type): string = string.decode(self.default_string_encoding) date_format = self._get_date_format(self.get_cls_attrs(cls)) try: if date_format is not None: dt = datetime.strptime(string, date_format) return date(dt.year, dt.month, dt.day) return self.date_from_unicode_iso(cls, string) except ValueError as e: match = cls._offset_re.match(string) if match: return date(int(match.group('year')), int(match.group('month')), int(match.group('day'))) else: raise ValidationError(string, "%%r: %s" % repr(e).replace("%", "%%")) def date_from_unicode(self, cls, string): date_format = self._get_date_format(self.get_cls_attrs(cls)) try: if date_format is not None: dt = datetime.strptime(string, date_format) return date(dt.year, dt.month, dt.day) return self.date_from_unicode_iso(cls, string) except ValueError as e: match = cls._offset_re.match(string) if match: return date(int(match.group('year')), int(match.group('month')), int(match.group('day'))) else: # the message from ValueError is quite nice already raise ValidationError(e.message, "%s") def duration_from_unicode(self, cls, string): duration = _duration_re.match(string).groupdict(0) if duration is None: raise ValidationError(string, "Time data '%%s' does not match regex '%s'" % (_duration_re.pattern,)) days = int(duration['days']) days += int(duration['months']) * 30 days += int(duration['years']) * 365 hours = int(duration['hours']) minutes = int(duration['minutes']) seconds = float(duration['seconds']) f, i = modf(seconds) seconds = i microseconds = int(1e6 * f) delta = timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds) if duration['sign'] == "-": delta *= -1 return delta def duration_from_bytes(self, cls, string): if isinstance(string, six.binary_type): string = string.decode(self.default_string_encoding) return self.duration_from_unicode(cls, string) def boolean_from_bytes(self, cls, string): return string.lower() in ('true', '1') def byte_array_from_bytes(self, cls, value, suggested_encoding=None): encoding = self.get_cls_attrs(cls).encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding return binary_decoding_handlers[encoding](value) def file_from_bytes(self, cls, value, suggested_encoding=None): encoding = self.get_cls_attrs(cls).encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding return File.Value(data=binary_decoding_handlers[encoding](value)) def complex_model_base_from_bytes(self, cls, string, **_): raise TypeError("Only primitives can be deserialized from string.") def array_from_bytes(self, cls, string, **_): if self.get_cls_attrs(cls).serialize_as != 'sd-list': raise TypeError("Only primitives can be deserialized from string.") # sd-list being space-delimited list. retval = [] inner_type, = cls._type_info.values() for s in string.split(): retval.append(self.from_bytes(inner_type, s)) return retval def xmlattribute_from_bytes(self, cls, value): return self.from_bytes(cls.type, value) def _datetime_from_unicode(self, cls, string): cls_attrs = self.get_cls_attrs(cls) # get parser parser = cls_attrs.parser # get date_format dt_format = cls_attrs.dt_format if dt_format is None: dt_format = cls_attrs.date_format if dt_format is None: dt_format = cls_attrs.out_format if dt_format is None: dt_format = cls_attrs.format # parse the string if parser is not None: retval = parser(self, cls, string) elif dt_format is not None: if six.PY2: # FIXME: perhaps it should encode to string's encoding instead # of utf8 all the time if isinstance(dt_format, six.text_type): dt_format = dt_format.encode('utf8') if isinstance(string, six.text_type): string = string.encode('utf8') retval = datetime.strptime(string, dt_format) astz = cls_attrs.as_timezone if astz: retval = retval.astimezone(cls_attrs.as_time_zone) else: retval = self.datetime_from_unicode_iso(cls, string) return retval _uuid_deserialize = { None: lambda s: uuid.UUID(s.decode('ascii') if isinstance(s, bytes) else s), 'hex': lambda s: uuid.UUID(hex=s), 'urn': lambda s: uuid.UUID(hex=s), 'bytes': lambda s: uuid.UUID(bytes=s), 'bytes_le': lambda s: uuid.UUID(bytes_le=s), 'fields': lambda s: uuid.UUID(fields=s), 'int': lambda s: uuid.UUID(int=s), ('int', int): lambda s: uuid.UUID(int=s), ('int', str): lambda s: uuid.UUID(int=int(s)), } if six.PY2: _uuid_deserialize[None] = lambda s: uuid.UUID(s) _uuid_deserialize[('int', long)] = _uuid_deserialize[('int', int)] def _parse_datetime_iso_match(date_match, tz=None): fields = date_match.groupdict() year = int(fields.get('year')) month = int(fields.get('month')) day = int(fields.get('day')) hour = int(fields.get('hr')) minute = int(fields.get('min')) second = int(fields.get('sec')) usecond = fields.get("sec_frac") if usecond is None: usecond = 0 else: # we only get the most significant 6 digits because that's what # datetime can handle. usecond = min(999999, int(round(float(usecond) * 1e6))) return datetime(year, month, day, hour, minute, second, usecond, tz) _dt_sec = lambda cls, val: \ int(mktime(val.timetuple())) _dt_sec_float = lambda cls, val: \ mktime(val.timetuple()) + (val.microsecond / 1e6) _dt_msec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) _dt_msec_float = lambda cls, val: \ mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) _dt_usec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000000 + val.microsecond _datetime_smap = { 'sec': _dt_sec, 'secs': _dt_sec, 'second': _dt_sec, 'seconds': _dt_sec, 'sec_float': _dt_sec_float, 'secs_float': _dt_sec_float, 'second_float': _dt_sec_float, 'seconds_float': _dt_sec_float, 'msec': _dt_msec, 'msecs': _dt_msec, 'msecond': _dt_msec, 'mseconds': _dt_msec, 'millisecond': _dt_msec, 'milliseconds': _dt_msec, 'msec_float': _dt_msec_float, 'msecs_float': _dt_msec_float, 'msecond_float': _dt_msec_float, 'mseconds_float': _dt_msec_float, 'millisecond_float': _dt_msec_float, 'milliseconds_float': _dt_msec_float, 'usec': _dt_usec, 'usecs': _dt_usec, 'usecond': _dt_usec, 'useconds': _dt_usec, 'microsecond': _dt_usec, 'microseconds': _dt_usec, } def _file_to_iter(f): try: data = f.read(65536) while len(data) > 0: yield data data = f.read(65536) finally: f.close() spyne-spyne-2.14.0/spyne/protocol/_outbase.py000066400000000000000000000733261417664205300212650ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function, unicode_literals import logging logger = logging.getLogger(__name__) import re import uuid import errno from os.path import isabs, join, abspath from collections import deque from datetime import datetime from decimal import Decimal as D from mmap import mmap, ACCESS_READ from time import mktime, strftime try: from lxml import etree from lxml import html except ImportError: etree = None html = None from spyne.protocol._base import ProtocolMixin from spyne.model import ModelBase, XmlAttribute, SimpleModel, Null, \ ByteArray, File, ComplexModelBase, AnyXml, AnyHtml, Unicode, Decimal, \ Double, Integer, Time, DateTime, Uuid, Duration, Boolean, AnyDict, \ AnyUri, PushBase, Date from spyne.model.relational import FileData from spyne.const.http import HTTP_400, HTTP_401, HTTP_404, HTTP_405, HTTP_413, \ HTTP_500 from spyne.error import Fault, InternalError, ResourceNotFoundError, \ RequestTooLongError, RequestNotAllowed, InvalidCredentialsError from spyne.model.binary import binary_encoding_handlers, \ BINARY_ENCODING_USE_DEFAULT from spyne.util import six from spyne.util.cdict import cdict class OutProtocolBase(ProtocolMixin): """This is the abstract base class for all out protocol implementations. Child classes can implement only the required subset of the public methods. An output protocol must implement :func:`serialize` and :func:`create_out_string`. The OutProtocolBase class supports the following events: * ``before_serialize``: Called before after the serialization operation is attempted. * ``after_serialize``: Called after the serialization operation is finished. The arguments the constructor takes are as follows: :param app: The application this protocol belongs to. :param mime_type: The mime_type this protocol should set for transports that support this. This is a quick way to override the mime_type by default instead of subclassing the releavant protocol implementation. :param ignore_uncap: Silently ignore cases when the protocol is not capable of serializing return values instead of raising a TypeError. """ def __init__(self, app=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, binary_encoding=None, string_encoding=None): super(OutProtocolBase, self).__init__(app=app, mime_type=mime_type, ignore_wrappers=ignore_wrappers, binary_encoding=binary_encoding, string_encoding=string_encoding) self.ignore_uncap = ignore_uncap self.message = None if mime_type is not None: self.mime_type = mime_type self._to_bytes_handlers = cdict({ ModelBase: self.model_base_to_bytes, File: self.file_to_bytes, Time: self.time_to_bytes, Uuid: self.uuid_to_bytes, Null: self.null_to_bytes, Date: self.date_to_bytes, Double: self.double_to_bytes, AnyXml: self.any_xml_to_bytes, Unicode: self.unicode_to_bytes, Boolean: self.boolean_to_bytes, Decimal: self.decimal_to_bytes, Integer: self.integer_to_bytes, AnyHtml: self.any_html_to_bytes, DateTime: self.datetime_to_bytes, Duration: self.duration_to_bytes, ByteArray: self.byte_array_to_bytes, XmlAttribute: self.xmlattribute_to_bytes, ComplexModelBase: self.complex_model_base_to_bytes, }) self._to_unicode_handlers = cdict({ ModelBase: self.model_base_to_unicode, File: self.file_to_unicode, Time: self.time_to_unicode, Date: self.date_to_unicode, Uuid: self.uuid_to_unicode, Null: self.null_to_unicode, Double: self.double_to_unicode, AnyXml: self.any_xml_to_unicode, AnyUri: self.any_uri_to_unicode, AnyDict: self.any_dict_to_unicode, AnyHtml: self.any_html_to_unicode, Unicode: self.unicode_to_unicode, Boolean: self.boolean_to_unicode, Decimal: self.decimal_to_unicode, Integer: self.integer_to_unicode, # FIXME: Would we need a to_unicode for localized dates? DateTime: self.datetime_to_unicode, Duration: self.duration_to_unicode, ByteArray: self.byte_array_to_unicode, XmlAttribute: self.xmlattribute_to_unicode, ComplexModelBase: self.complex_model_base_to_unicode, }) self._to_bytes_iterable_handlers = cdict({ File: self.file_to_bytes_iterable, ByteArray: self.byte_array_to_bytes_iterable, ModelBase: self.model_base_to_bytes_iterable, SimpleModel: self.simple_model_to_bytes_iterable, ComplexModelBase: self.complex_model_to_bytes_iterable, }) def serialize(self, ctx, message): """Serializes ``ctx.out_object``. If ctx.out_stream is not None, ``ctx.out_document`` and ``ctx.out_string`` are skipped and the response is written directly to ``ctx.out_stream``. :param ctx: :class:`MethodContext` instance. :param message: One of ``(ProtocolBase.REQUEST, ProtocolBase.RESPONSE)``. """ def create_out_string(self, ctx, out_string_encoding=None): """Uses ctx.out_document to set ctx.out_string""" def fault_to_http_response_code(self, fault): """Special function to convert native Python exceptions to Http response codes. """ if isinstance(fault, RequestTooLongError): return HTTP_413 if isinstance(fault, ResourceNotFoundError): return HTTP_404 if isinstance(fault, RequestNotAllowed): return HTTP_405 if isinstance(fault, InvalidCredentialsError): return HTTP_401 if isinstance(fault, Fault) and (fault.faultcode.startswith('Client.') or fault.faultcode == 'Client'): return HTTP_400 return HTTP_500 def set_validator(self, validator): """You must override this function if you want your protocol to support validation.""" assert validator is None self.validator = None def to_bytes(self, cls, value, *args, **kwargs): if value is None: return None handler = self._to_bytes_handlers[cls] retval = handler(cls, value, *args, **kwargs) # enable this only for testing. we're not as strict for performance # reasons # assert isinstance(retval, six.binary_type), \ # "AssertionError: %r %r %r handler: %r" % \ # (type(retval), six.binary_type, retval, handler) return retval def to_unicode(self, cls, value, *args, **kwargs): if value is None: return None handler = self._to_unicode_handlers[cls] retval = handler(cls, value, *args, **kwargs) # enable this only for testing. we're not as strict for performance # reasons as well as not to take the joy of dealing with duck typing # from the user # assert isinstance(retval, six.text_type), \ # "AssertionError: %r %r handler: %r" % \ # (type(retval), retval, handler) return retval def to_bytes_iterable(self, cls, value): if value is None: return [] if isinstance(value, PushBase): return value handler = self._to_bytes_iterable_handlers[cls] return handler(cls, value) def null_to_bytes(self, cls, value, **_): return b"" def null_to_unicode(self, cls, value, **_): return u"" def any_xml_to_bytes(self, cls, value, **_): return etree.tostring(value) def any_xml_to_unicode(self, cls, value, **_): return etree.tostring(value, encoding='unicode') def any_dict_to_unicode(self, cls, value, **_): return repr(value) def any_html_to_bytes(self, cls, value, **_): return html.tostring(value) def any_html_to_unicode(self, cls, value, **_): return html.tostring(value, encoding='unicode') def uuid_to_bytes(self, cls, value, suggested_encoding=None, **_): ser_as = self.get_cls_attrs(cls).serialize_as retval = self.uuid_to_unicode(cls, value, suggested_encoding=suggested_encoding, **_) if ser_as in ('bytes', 'bytes_le', 'fields', 'int', six.binary_type): return retval return retval.encode('ascii') def uuid_to_unicode(self, cls, value, suggested_encoding=None, **_): attr = self.get_cls_attrs(cls) ser_as = attr.serialize_as encoding = attr.encoding if encoding is None: encoding = suggested_encoding retval = _uuid_serialize[ser_as](value) if ser_as in ('bytes', 'bytes_le'): retval = binary_encoding_handlers[encoding]((retval,)) return retval def unicode_to_bytes(self, cls, value, **_): retval = value cls_attrs = self.get_cls_attrs(cls) if isinstance(value, six.text_type): if cls_attrs.encoding is not None: retval = value.encode(cls_attrs.encoding) elif self.default_string_encoding is not None: retval = value.encode(self.default_string_encoding) elif not six.PY2: logger.warning("You need to set either an encoding for %r " "or a default_string_encoding for %r", cls, self) if cls_attrs.str_format is not None: return cls_attrs.str_format.format(value) elif cls_attrs.format is not None: return cls_attrs.format % retval return retval def any_uri_to_unicode(self, cls, value, **_): return self.unicode_to_unicode(cls, value, **_) def unicode_to_unicode(self, cls, value, **_): # :))) cls_attrs = self.get_cls_attrs(cls) retval = value if isinstance(value, six.binary_type): if cls_attrs.encoding is not None: retval = value.decode(cls_attrs.encoding) if self.default_string_encoding is not None: retval = value.decode(self.default_string_encoding) elif not six.PY2: logger.warning("You need to set either an encoding for %r " "or a default_string_encoding for %r", cls, self) if cls_attrs.str_format is not None: return cls_attrs.str_format.format(value) elif cls_attrs.format is not None: return cls_attrs.format % retval return retval def decimal_to_bytes(self, cls, value, **_): return self.decimal_to_unicode(cls, value, **_).encode('utf8') def decimal_to_unicode(self, cls, value, **_): D(value) # sanity check cls_attrs = self.get_cls_attrs(cls) if cls_attrs.str_format is not None: return cls_attrs.str_format.format(value) elif cls_attrs.format is not None: return cls_attrs.format % value return str(value) def double_to_bytes(self, cls, value, **_): return self.double_to_unicode(cls, value, **_).encode('utf8') def double_to_unicode(self, cls, value, **_): float(value) # sanity check cls_attrs = self.get_cls_attrs(cls) if cls_attrs.str_format is not None: return cls_attrs.str_format.format(value) elif cls_attrs.format is not None: return cls_attrs.format % value return repr(value) def integer_to_bytes(self, cls, value, **_): return self.integer_to_unicode(cls, value, **_).encode('utf8') def integer_to_unicode(self, cls, value, **_): int(value) # sanity check cls_attrs = self.get_cls_attrs(cls) if cls_attrs.str_format is not None: return cls_attrs.str_format.format(value) elif cls_attrs.format is not None: return cls_attrs.format % value return str(value) def time_to_bytes(self, cls, value, **kwargs): return self.time_to_unicode(cls, value, **kwargs) def time_to_unicode(self, cls, value, **_): """Returns ISO formatted times.""" if isinstance(value, datetime): value = value.time() return value.isoformat() def date_to_bytes(self, cls, val, **_): return self.date_to_unicode(cls, val, **_).encode("utf8") def date_to_unicode(self, cls, val, **_): if isinstance(val, datetime): val = val.date() sa = self.get_cls_attrs(cls).serialize_as if sa is None or sa in (str, 'str'): return self._date_to_bytes(cls, val) return _datetime_smap[sa](cls, val) def datetime_to_bytes(self, cls, val, **_): retval = self.datetime_to_unicode(cls, val, **_) sa = self.get_cls_attrs(cls).serialize_as if sa is None or sa in (six.text_type, str, 'str'): return retval.encode('ascii') return retval def datetime_to_unicode(self, cls, val, **_): sa = self.get_cls_attrs(cls).serialize_as if sa is None or sa in (six.text_type, str, 'str'): return self._datetime_to_unicode(cls, val) return _datetime_smap[sa](cls, val) def duration_to_bytes(self, cls, value, **_): return self.duration_to_unicode(cls, value, **_).encode("utf8") def duration_to_unicode(self, cls, value, **_): if value.days < 0: value = -value negative = True else: negative = False tot_sec = int(value.total_seconds()) seconds = value.seconds % 60 minutes = value.seconds // 60 hours = minutes // 60 minutes %= 60 seconds = float(seconds) useconds = value.microseconds retval = deque() if negative: retval.append("-P") else: retval.append("P") if value.days != 0: retval.append("%iD" % value.days) if tot_sec != 0 and tot_sec % 86400 == 0 and useconds == 0: return ''.join(retval) retval.append('T') if hours > 0: retval.append("%iH" % hours) if minutes > 0: retval.append("%iM" % minutes) if seconds > 0 or useconds > 0: retval.append("%i" % seconds) if useconds > 0: retval.append(".%i" % useconds) retval.append("S") if len(retval) == 2: retval.append('0S') return ''.join(retval) def boolean_to_bytes(self, cls, value, **_): return str(bool(value)).lower().encode('ascii') def boolean_to_unicode(self, cls, value, **_): return str(bool(value)).lower() def byte_array_to_bytes(self, cls, value, suggested_encoding=None, **_): cls_attrs = self.get_cls_attrs(cls) encoding = cls_attrs.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: if suggested_encoding is None: encoding = self.binary_encoding else: encoding = suggested_encoding if encoding is None and isinstance(value, (list, tuple)) \ and len(value) == 1 and isinstance(value[0], mmap): return value[0] encoder = binary_encoding_handlers[encoding] logger.debug("Using binary encoder %r for encoding %r", encoder, encoding) retval = encoder(value) if encoding is not None and isinstance(retval, six.text_type): retval = retval.encode('ascii') return retval def byte_array_to_unicode(self, cls, value, suggested_encoding=None, **_): encoding = self.get_cls_attrs(cls).encoding if encoding is BINARY_ENCODING_USE_DEFAULT: if suggested_encoding is None: encoding = self.binary_encoding else: encoding = suggested_encoding if encoding is None: raise ValueError("Arbitrary binary data can't be serialized to " "unicode") retval = binary_encoding_handlers[encoding](value) if not isinstance(retval, six.text_type): retval = retval.decode('ascii') return retval def byte_array_to_bytes_iterable(self, cls, value, **_): return value def file_to_bytes(self, cls, value, suggested_encoding=None): """ :param cls: A :class:`spyne.model.File` subclass :param value: Either a sequence of byte chunks or a :class:`spyne.model.File.Value` instance. """ encoding = self.get_cls_attrs(cls).encoding if encoding is BINARY_ENCODING_USE_DEFAULT: if suggested_encoding is None: encoding = self.binary_encoding else: encoding = suggested_encoding if isinstance(value, File.Value): if value.data is not None: return binary_encoding_handlers[encoding](value.data) if value.handle is not None: # maybe we should have used the sweeping except: here. if hasattr(value.handle, 'fileno'): if six.PY2: fileno = value.handle.fileno() data = (mmap(fileno, 0, access=ACCESS_READ),) else: import io try: fileno = value.handle.fileno() data = mmap(fileno, 0, access=ACCESS_READ) except io.UnsupportedOperation: data = (value.handle.read(),) else: data = (value.handle.read(),) return binary_encoding_handlers[encoding](data) if value.path is not None: handle = open(value.path, 'rb') fileno = handle.fileno() data = mmap(fileno, 0, access=ACCESS_READ) return binary_encoding_handlers[encoding](data) assert False, "Unhandled file type" if isinstance(value, FileData): try: return binary_encoding_handlers[encoding](value.data) except Exception as e: logger.error("Error encoding value to binary. Error: %r, Value: %r", e, value) raise try: return binary_encoding_handlers[encoding](value) except Exception as e: logger.error("Error encoding value to binary. Error: %r, Value: %r", e, value) raise def file_to_unicode(self, cls, value, suggested_encoding=None): """ :param cls: A :class:`spyne.model.File` subclass :param value: Either a sequence of byte chunks or a :class:`spyne.model.File.Value` instance. """ cls_attrs = self.get_cls_attrs(cls) encoding = cls_attrs.encoding if encoding is BINARY_ENCODING_USE_DEFAULT: encoding = suggested_encoding if encoding is None and cls_attrs.mode is File.TEXT: raise ValueError("Arbitrary binary data can't be serialized to " "unicode.") retval = self.file_to_bytes(cls, value, suggested_encoding) if not isinstance(retval, six.text_type): retval = retval.decode('ascii') return retval def file_to_bytes_iterable(self, cls, value, **_): if value.data is not None: if isinstance(value.data, (list, tuple)) and \ isinstance(value.data[0], mmap): return _file_to_iter(value.data[0]) return iter(value.data) if value.handle is not None: f = value.handle f.seek(0) return _file_to_iter(f) assert value.path is not None, "You need to write data to " \ "persistent storage first if you want to read it back." try: path = value.path if not isabs(value.path): path = join(value.store, value.path) assert abspath(path).startswith(value.store), \ "No relative paths are allowed" return _file_to_iter(open(path, 'rb')) except IOError as e: if e.errno == errno.ENOENT: raise ResourceNotFoundError(value.path) else: raise InternalError("Error accessing requested file") def simple_model_to_bytes_iterable(self, cls, value, **kwargs): retval = self.to_bytes(cls, value, **kwargs) if retval is None: return (b'',) return (retval,) def complex_model_to_bytes_iterable(self, cls, value, **_): if self.ignore_uncap: return tuple() raise TypeError("This protocol can only serialize primitives.") def complex_model_base_to_bytes(self, cls, value, **_): raise TypeError("Only primitives can be serialized to string.") def complex_model_base_to_unicode(self, cls, value, **_): raise TypeError("Only primitives can be serialized to string.") def xmlattribute_to_bytes(self, cls, string, **kwargs): return self.to_bytes(cls.type, string, **kwargs) def xmlattribute_to_unicode(self, cls, string, **kwargs): return self.to_unicode(cls.type, string, **kwargs) def model_base_to_bytes_iterable(self, cls, value, **kwargs): return cls.to_bytes_iterable(value, **kwargs) def model_base_to_bytes(self, cls, value, **kwargs): return cls.to_bytes(value, **kwargs) def model_base_to_unicode(self, cls, value, **kwargs): return cls.to_unicode(value, **kwargs) def _datetime_to_unicode(self, cls, value, **_): """Returns ISO formatted datetimes.""" cls_attrs = self.get_cls_attrs(cls) if cls_attrs.as_timezone is not None and value.tzinfo is not None: value = value.astimezone(cls_attrs.as_timezone) if not cls_attrs.timezone: value = value.replace(tzinfo=None) dt_format = self._get_datetime_format(cls_attrs) if dt_format is None: retval = value.isoformat() elif six.PY2 and isinstance(dt_format, unicode): retval = self.strftime(value, dt_format.encode('utf8')).decode('utf8') else: retval = self.strftime(value, dt_format) # FIXME: must deprecate string_format, this should have been str_format str_format = cls_attrs.string_format if str_format is None: str_format = cls_attrs.str_format if str_format is not None: return str_format.format(value) # FIXME: must deprecate interp_format, this should have been just format interp_format = cls_attrs.interp_format if interp_format is not None: return interp_format.format(value) return retval def _date_to_bytes(self, cls, value, **_): cls_attrs = self.get_cls_attrs(cls) date_format = cls_attrs.date_format if date_format is None: retval = value.isoformat() elif six.PY2 and isinstance(date_format, unicode): date_format = date_format.encode('utf8') retval = self.strftime(value, date_format).decode('utf8') else: retval = self.strftime(value, date_format) str_format = cls_attrs.str_format if str_format is not None: return str_format.format(value) format = cls_attrs.format if format is not None: return format.format(value) return retval # Format a datetime through its full proleptic Gregorian date range. # http://code.activestate.com/recipes/ # 306860-proleptic-gregorian-dates-and-strftime-before-1900/ # http://stackoverflow.com/a/32206673 # # >>> strftime(datetime.date(1850, 8, 2), "%Y/%M/%d was a %A") # '1850/00/02 was a Friday' # >>> # remove the unsupposed "%s" command. But don't # do it if there's an even number of %s before the s # because those are all escaped. Can't simply # remove the s because the result of # %sY # should be %Y if %s isn't supported, not the # 4 digit year. _illegal_s = re.compile(r"((^|[^%])(%%)*%s)") @staticmethod def _findall_datetime(text, substr): # Also finds overlaps sites = [] i = 0 while 1: j = text.find(substr, i) if j == -1: break sites.append(j) i=j+1 return sites # Every 28 years the calendar repeats, except through century leap # years where it's 6 years. But only if you're using the Gregorian # calendar. ;) @classmethod def strftime(cls, dt, fmt): if cls._illegal_s.search(fmt): raise TypeError("This strftime implementation does not handle %s") if dt.year > 1900: return dt.strftime(fmt) year = dt.year # For every non-leap year century, advance by # 6 years to get into the 28-year repeat cycle delta = 2000 - year off = 6*(delta // 100 + delta // 400) year += off # Move to around the year 2000 year += ((2000 - year) // 28) * 28 timetuple = dt.timetuple() s1 = strftime(fmt, (year,) + timetuple[1:]) sites1 = cls._findall_datetime(s1, str(year)) s2 = strftime(fmt, (year+28,) + timetuple[1:]) sites2 = cls._findall_datetime(s2, str(year+28)) sites = [] for site in sites1: if site in sites2: sites.append(site) s = s1 syear = "%4d" % (dt.year,) for site in sites: s = s[:site] + syear + s[site+4:] return s _uuid_serialize = { None: str, str: str, 'str': str, 'hex': lambda u: u.hex, 'urn': lambda u: u.urn, 'bytes': lambda u: u.bytes, 'bytes_le': lambda u: u.bytes_le, 'fields': lambda u: u.fields, int: lambda u: u.int, 'int': lambda u: u.int, } _uuid_deserialize = { None: uuid.UUID, str: uuid.UUID, 'str': uuid.UUID, 'hex': lambda s: uuid.UUID(hex=s), 'urn': lambda s: uuid.UUID(hex=s), 'bytes': lambda s: uuid.UUID(bytes=s), 'bytes_le': lambda s: uuid.UUID(bytes_le=s), 'fields': lambda s: uuid.UUID(fields=s), int: lambda s: uuid.UUID(int=s), 'int': lambda s: uuid.UUID(int=s), (int, int): lambda s: uuid.UUID(int=s), ('int', int): lambda s: uuid.UUID(int=s), (int, str): lambda s: uuid.UUID(int=int(s)), ('int', str): lambda s: uuid.UUID(int=int(s)), } if six.PY2: _uuid_deserialize[('int', long)] = _uuid_deserialize[('int', int)] _uuid_deserialize[(int, long)] = _uuid_deserialize[('int', int)] def _parse_datetime_iso_match(date_match, tz=None): fields = date_match.groupdict() year = int(fields.get('year')) month = int(fields.get('month')) day = int(fields.get('day')) hour = int(fields.get('hr')) minute = int(fields.get('min')) second = int(fields.get('sec')) usecond = fields.get("sec_frac") if usecond is None: usecond = 0 else: # we only get the most significant 6 digits because that's what # datetime can handle. usecond = int(round(float(usecond) * 1e6)) return datetime(year, month, day, hour, minute, second, usecond, tz) _dt_sec = lambda cls, val: \ int(mktime(val.timetuple())) _dt_sec_float = lambda cls, val: \ mktime(val.timetuple()) + (val.microsecond / 1e6) _dt_msec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000 + (val.microsecond // 1000) _dt_msec_float = lambda cls, val: \ mktime(val.timetuple()) * 1000 + (val.microsecond / 1000.0) _dt_usec = lambda cls, val: \ int(mktime(val.timetuple())) * 1000000 + val.microsecond _datetime_smap = { 'sec': _dt_sec, 'secs': _dt_sec, 'second': _dt_sec, 'seconds': _dt_sec, 'sec_float': _dt_sec_float, 'secs_float': _dt_sec_float, 'second_float': _dt_sec_float, 'seconds_float': _dt_sec_float, 'msec': _dt_msec, 'msecs': _dt_msec, 'msecond': _dt_msec, 'mseconds': _dt_msec, 'millisecond': _dt_msec, 'milliseconds': _dt_msec, 'msec_float': _dt_msec_float, 'msecs_float': _dt_msec_float, 'msecond_float': _dt_msec_float, 'mseconds_float': _dt_msec_float, 'millisecond_float': _dt_msec_float, 'milliseconds_float': _dt_msec_float, 'usec': _dt_usec, 'usecs': _dt_usec, 'usecond': _dt_usec, 'useconds': _dt_usec, 'microsecond': _dt_usec, 'microseconds': _dt_usec, } def _file_to_iter(f): try: data = f.read(8192) while len(data) > 0: yield data data = f.read(8192) finally: f.close() META_ATTR = ['nullable', 'default_factory'] spyne-spyne-2.14.0/spyne/protocol/cloth/000077500000000000000000000000001417664205300202105ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/cloth/__init__.py000066400000000000000000000020211417664205300223140ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.cloth`` package contains an EXPERIMENTAL protocol for clothing otherwise boring data. """ from spyne.protocol.cloth._base import XmlCloth # huge hack to have the last line of microformat.py execute import spyne.protocol.html spyne-spyne-2.14.0/spyne/protocol/cloth/_base.py000066400000000000000000000266501417664205300216440ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from lxml import etree from lxml.etree import LxmlSyntaxError from spyne import ProtocolContext, BODY_STYLE_WRAPPED, ByteArray, File, Array from spyne.util import Break, coroutine from spyne.protocol import ProtocolMixin from spyne.protocol.cloth.to_parent import ToParentMixin from spyne.protocol.cloth.to_cloth import ToClothMixin from spyne.util.six import BytesIO from spyne.util.color import R, B from spyne.util.tlist import tlist class XmlClothProtocolContext(ProtocolContext): def __init__(self, parent, transport, type=None): super(XmlClothProtocolContext, self).__init__(parent, transport, type) self.inst_stack = tlist([], tuple) self.prot_stack = tlist([], ProtocolMixin) self.doctype_written = False class XmlCloth(ToParentMixin, ToClothMixin): mime_type = 'text/xml' HtmlMicroFormat = None def __init__(self, app=None, encoding='utf8', doctype=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, cloth=None, cloth_parser=None, polymorphic=True, strip_comments=True, use_ns=None, skip_root_tag=False): super(XmlCloth, self).__init__(app=app, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, polymorphic=polymorphic) self._init_cloth(cloth, cloth_parser, strip_comments) self.developer_mode = False self.encoding = encoding self.default_method = 'xml' self.doctype = doctype self.use_ns = use_ns self.skip_root_tag = skip_root_tag def get_context(self, parent, transport): return XmlClothProtocolContext(parent, transport) def serialize(self, ctx, message): """Uses ``ctx.out_object``, ``ctx.out_header`` or ``ctx.out_error`` to set ``ctx.out_body_doc``, ``ctx.out_header_doc`` and ``ctx.out_document`` as an ``lxml.etree._Element instance``. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_stream is None: ctx.out_stream = BytesIO() logger.debug("%r %d", ctx.out_stream, id(ctx.out_stream)) if ctx.out_error is not None: # All errors at this point must be Fault subclasses. inst = ctx.out_error cls = inst.__class__ name = cls.get_type_name() if self.developer_mode: # FIXME: the eff is this? ctx.out_object = (inst,) retval = self._incgen(ctx, cls, inst, name) else: with self.docfile(ctx.out_stream, encoding=self.encoding) as xf: retval = self.to_parent(ctx, cls, inst, xf, name) else: assert message is self.RESPONSE result_class = ctx.descriptor.out_message name = result_class.get_type_name() if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: if self.ignore_wrappers: result_inst = ctx.out_object[0] while result_class.Attributes._wrapper and \ len(result_class._type_info) == 1: result_class, = result_class._type_info.values() else: result_inst = result_class() for i, attr_name in enumerate( result_class._type_info.keys()): setattr(result_inst, attr_name, ctx.out_object[i]) else: result_inst, = ctx.out_object retval = self._incgen(ctx, result_class, result_inst, name) self.event_manager.fire_event('after_serialize', ctx) return retval def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string if the output is a StringIO object, which means we're run by a sync framework. Async frameworks have the out_stream write directly to the output stream so out_string should not be used. """ if isinstance(ctx.out_stream, BytesIO): ctx.out_string = [ctx.out_stream.getvalue()] @coroutine def _incgen(self, ctx, cls, inst, name): """Entry point to the (stack of) XmlCloth-based protocols. Not supposed to be overridden. """ if name is None: name = cls.get_type_name() try: with self.docfile(ctx.out_stream, encoding=self.encoding) as xf: ctx.outprot_ctx.doctype_written = False ctx.protocol.prot_stack = tlist([], ProtocolMixin) ret = self.subserialize(ctx, cls, inst, xf, name) if isgenerator(ret): # Poor man's yield from try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass except LxmlSyntaxError as e: if e.msg == 'no content written': pass else: raise def docfile(self, *args, **kwargs): logger.debug("Starting file with %r %r", args, kwargs) return etree.xmlfile(*args, **kwargs) def _get_doctype(self, cloth): if self.doctype is not None: return self.doctype if cloth is not None: return cloth.getroottree().docinfo.doctype if self._root_cloth is not None: return self._root_cloth.getroottree().docinfo.doctype if self._cloth is not None: return self._cloth.getroottree().docinfo.doctype def write_doctype(self, ctx, parent, cloth=None): dt = self._get_doctype(cloth) if dt is None: return parent.write_doctype(dt) ctx.outprot_ctx.doctype_written = True logger.debug("Doctype written as: '%s'", dt) @staticmethod def get_class_cloth(cls): return cls.Attributes._xml_cloth @staticmethod def get_class_root_cloth(cls): return cls.Attributes._xml_root_cloth def check_class_cloths(self, ctx, cls, inst, parent, name, **kwargs): c = self.get_class_root_cloth(cls) eltstack = getattr(ctx.protocol, 'eltstack', []) if c is not None and len(eltstack) == 0 and not (eltstack[-1] is c): if not ctx.outprot_ctx.doctype_written: self.write_doctype(ctx, parent, c) logger.debug("to object root cloth") return True, self.to_root_cloth(ctx, cls, inst, c, parent, name, **kwargs) c = self.get_class_cloth(cls) if c is not None: if not ctx.outprot_ctx.doctype_written: self.write_doctype(ctx, parent, c) logger.debug("to object cloth") return True, self.to_parent_cloth(ctx, cls, inst, c, parent, name, **kwargs) return False, None @coroutine def subserialize(self, ctx, cls, inst, parent, name='', **kwargs): """Bridge between multiple XmlCloth-based protocols. Not supposed to be overridden. """ pstack = ctx.protocol.prot_stack pstack.append(self) logger.debug("%s push prot %r. newlen: %d", R("%"), self, len(pstack)) have_cloth = False cls_cloth = self.get_class_cloth(cls) if cls_cloth is not None: logger.debug("to object cloth for %s", cls.get_type_name()) ret = self.to_parent_cloth(ctx, cls, inst, cls_cloth, parent, name) elif self._root_cloth is not None: logger.debug("to root cloth for %s", cls.get_type_name()) ret = self.to_root_cloth(ctx, cls, inst, self._root_cloth, parent, name) have_cloth = True elif self._cloth is not None: logger.debug("to parent protocol cloth for %s", cls.get_type_name()) ret = self.to_parent_cloth(ctx, cls, inst, self._cloth, parent, name) have_cloth = True else: logger.debug("to parent for %s", cls.get_type_name()) ret = self.start_to_parent(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): # Poor man's yield from try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass finally: self._finalize_protocol(ctx, parent, have_cloth) else: self._finalize_protocol(ctx, parent, have_cloth) pstack.pop() logger.debug("%s pop prot %r. newlen: %d", B("%"), self, len(pstack)) def _finalize_protocol(self, ctx, parent, have_cloth): if have_cloth: self._close_cloth(ctx, parent) return if len(ctx.protocol.prot_stack) == 1 and len(ctx.protocol.eltstack) > 0: self._close_cloth(ctx, parent) return @staticmethod def _gen_tagname(ns, name): if ns is not None: name = "{%s}%s" % (ns, name) return name def _gen_attrib_dict(self, inst, fti): attrs = {} for field_name, field_type in fti.attrs.items(): ns = field_type._ns if ns is None: ns = field_type.Attributes.sub_ns sub_name = field_type.Attributes.sub_name if sub_name is None: sub_name = field_name val = getattr(inst, field_name, None) sub_name = self._gen_tagname(ns, sub_name) if issubclass(field_type.type, (ByteArray, File)): valstr = self.to_unicode(field_type.type, val, self.binary_encoding) else: valstr = self.to_unicode(field_type.type, val) if valstr is not None: attrs[sub_name] = valstr return attrs def decompose_incoming_envelope(self, ctx, message): raise NotImplementedError("This is an output-only protocol.") spyne-spyne-2.14.0/spyne/protocol/cloth/to_cloth.py000066400000000000000000001027561417664205300224100ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger_c = logging.getLogger("%s.cloth" % __name__) logger_s = logging.getLogger("%s.serializer" % __name__) from lxml import html, etree from copy import deepcopy from inspect import isgenerator from spyne.util import Break, coroutine from spyne.util.oset import oset from spyne.util.six import string_types from spyne.util.color import R, B from spyne.model import Array, AnyXml, AnyHtml, ModelBase, ComplexModelBase, \ PushBase, XmlAttribute, AnyUri, XmlData, Any from spyne.protocol import OutProtocolBase from spyne.util.cdict import cdict _revancestors = lambda elt: list(reversed(tuple(elt.iterancestors()))) _NODATA = type("_NODATA", (object,), {}) def _prevsibls(elt, strip_comments, since=None): return reversed(list(_prevsibls_since(elt, strip_comments, since))) def _prevsibls_since(elt, strip_comments, since): if since is elt: return for prevsibl in elt.itersiblings(preceding=True): if prevsibl is since: break if strip_comments and isinstance(elt, etree.CommentBase): if elt.text.startswith('[if ') and elt.text.endswith('[endif]'): pass else: continue yield prevsibl def _set_identifier_prefix(obj, prefix, mrpc_id='mrpc', id_attr='id', data_tag='data', data_attr='data', attr_attr='attr', root_attr='root', tagbag_attr='tagbag'): obj.ID_PREFIX = prefix obj.MRPC_ID = '{}{}'.format(prefix, mrpc_id) obj.ID_ATTR_NAME = '{}{}'.format(prefix, id_attr) obj.DATA_TAG_NAME = '{}{}'.format(prefix, data_tag) obj.DATA_ATTR_NAME = '{}{}'.format(prefix, data_attr) obj.ATTR_ATTR_NAME = '{}{}'.format(prefix, attr_attr) obj.ROOT_ATTR_NAME = '{}{}'.format(prefix, root_attr) obj.TAGBAG_ATTR_NAME = '{}{}'.format(prefix, tagbag_attr) # FIXME: get rid of this. We don't want logic creep inside cloths obj.WRITE_CONTENTS_WHEN_NOT_NONE = '{}write-contents'.format(prefix) obj.SPYNE_ATTRS = { obj.ID_ATTR_NAME, obj.DATA_ATTR_NAME, obj.ATTR_ATTR_NAME, obj.ROOT_ATTR_NAME, obj.TAGBAG_ATTR_NAME, obj.WRITE_CONTENTS_WHEN_NOT_NONE, } class ClothParserMixin(object): ID_PREFIX = 'spyne-' # these are here for documentation purposes. The are all reinitialized with # the call ta _set_identifier_prefix below the class definition ID_ATTR_NAME = 'spyne-id' DATA_TAG_NAME = 'spyne-data' DATA_ATTR_NAME = 'spyne-data' ATTR_ATTR_NAME = 'spyne-attr' ROOT_ATTR_NAME = 'spyne-root' TAGBAG_ATTR_NAME = 'spyne-tagbag' WRITE_CONTENTS_WHEN_NOT_NONE = 'spyne-write-contents' def set_identifier_prefix(self, what): _set_identifier_prefix(self, what) return self @classmethod def from_xml_cloth(cls, cloth, strip_comments=True): retval = cls() retval._init_cloth(cloth, cloth_parser=etree.XMLParser(), strip_comments=strip_comments) return retval @classmethod def from_html_cloth(cls, cloth, strip_comments=True): retval = cls() retval._init_cloth(cloth, cloth_parser=html.HTMLParser(), strip_comments=strip_comments) return retval @staticmethod def _strip_comments(root): for elt in root.iter(): if isinstance(elt, etree.CommentBase): if elt.getparent() is not None: if elt.text.startswith('[if ') \ and elt.text.endswith('[endif]'): pass else: elt.getparent().remove(elt) def _parse_file(self, file_name, cloth_parser): cloth = etree.parse(file_name, parser=cloth_parser) return cloth.getroot() def _init_cloth(self, cloth, cloth_parser, strip_comments): """Called from XmlCloth.__init__ in order to not break the dunder init signature consistency""" self._cloth = None self._root_cloth = None self.strip_comments = strip_comments self._mrpc_cloth = self._root_cloth = None if cloth is None: return if isinstance(cloth, string_types): cloth = self._parse_file(cloth, cloth_parser) if strip_comments: self._strip_comments(cloth) q = "//*[@%s]" % self.ROOT_ATTR_NAME elts = cloth.xpath(q) if len(elts) > 0: logger_c.debug("Using %r as root cloth.", cloth) self._root_cloth = elts[0] else: logger_c.debug("Using %r as plain cloth.", cloth) self._cloth = cloth self._mrpc_cloth = self._pop_elt(cloth, 'mrpc_entry') def _pop_elt(self, elt, what): query = '//*[@%s="%s"]' % (self.ID_ATTR_NAME, what) retval = elt.xpath(query) if len(retval) > 1: raise ValueError("more than one element found for query %r" % query) elif len(retval) == 1: retval = retval[0] next(retval.iterancestors()).remove(retval) return retval _set_identifier_prefix(ClothParserMixin, ClothParserMixin.ID_PREFIX) class ToClothMixin(OutProtocolBase, ClothParserMixin): def __init__(self, app=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, polymorphic=True): super(ToClothMixin, self).__init__(app=app, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) self.polymorphic = polymorphic self.rendering_handlers = cdict({ ModelBase: self.model_base_to_cloth, AnyXml: self.xml_to_cloth, Any: self.any_to_cloth, AnyHtml: self.html_to_cloth, AnyUri: self.any_uri_to_cloth, ComplexModelBase: self.complex_to_cloth, }) def _get_elts(self, elt, tag_id=None): if tag_id is None: return elt.xpath('.//*[@*[starts-with(name(), "%s")]]' % self.ID_PREFIX) return elt.xpath('.//*[@*[starts-with(name(), "%s")]="%s"]' % ( self.ID_PREFIX, tag_id)) def _get_outmost_elts(self, tmpl, tag_id=None): ids = set() # we assume xpath() returns elements in top to bottom (or outside to # inside) order. for elt in self._get_elts(tmpl, tag_id): if elt is tmpl: # FIXME: kill this logger_c.debug("Don't send myself") continue # don't send myself if len(set((id(e) for e in elt.iterancestors())) & ids) > 0: logger_c.debug("Don't send grandchildren") continue # don't send grandchildren if id(elt) in ids: # FIXME: this check should be safe to remove logger_c.debug("Don't send what's already been sent") continue # don't send what's already been sent if self.ID_ATTR_NAME in elt.attrib: # Prevent primitive attrs like spyne-attr from interfering # with elt descent ids.add(id(elt)) yield elt def _get_clean_elt(self, elt, what): query = '//*[@%s="%s"]' % (self.ID_ATTR_NAME, what) retval = elt.xpath(query) if len(retval) > 1: raise ValueError("more than one element found for query %r" % query) elif len(retval) == 1: retval = retval[0] del retval.attrib[self.ID_ATTR_NAME] return retval def _get_elts_by_id(self, elt, what): retval = elt.xpath('//*[@id="%s"]' % what) logger_c.debug("id=%r got %r", what, retval) return retval def _is_tagbag(self, elt): return self.TAGBAG_ATTR_NAME in elt.attrib @staticmethod def _methods(ctx, cls, inst): while cls.Attributes._wrapper and len(cls._type_info) > 0: cls, = cls._type_info.values() if cls.Attributes.methods is not None: for k, v in cls.Attributes.methods.items(): is_shown = True if v.when is not None: is_shown = v.when(inst, ctx) if is_shown: yield k, v def _actions_to_cloth(self, ctx, cls, inst, template): if self._mrpc_cloth is None: logger_c.warning("missing 'mrpc_template'") return for elt in self._get_elts(template, self.MRPC_ID): for k, v in self._methods(ctx, cls, inst): href = v.in_message.get_type_name() text = v.translate(ctx.locale, v.in_message.get_type_name()) mrpc_template = deepcopy(self._mrpc_cloth) anchor = self._get_clean_elt(mrpc_template, 'mrpc_link') anchor.attrib['href'] = href text_elt = self._get_clean_elt(mrpc_template, 'mrpc_text') if text_elt is not None: text_elt.text = text else: anchor.text = text elt.append(mrpc_template) # mutable default ok because readonly def _enter_cloth(self, ctx, cloth, parent, attrib={}, skip=False, method=None, skip_dupe=False): """Enters the given tag in the document by using the shortest path from current tag. 1. Moves up the tree by writing all tags so that the set of ancestors of the current tag are a subset of the ancestors of the parent tag 2. Writes all tags until hitting a direct ancestor, enters it, and keeps writing previous siblings of ancestor tags and entering ancestor tags until hitting the target tag. 3. Enters the target tag and returns There is no _exit_cloth because exiting from tags is done automatically with subsequent calls to _enter_cloth and finally to _close_cloth. :param ctx: A MethodContext instance :param cloth: The target cloth -- an ``lxml.etree._Element`` instance. :param parent: The target stream -- typically an ``lxml.etree._IncrementalFileWriter`` instance. :param attrib: A dict of additional attributes for the target cloth. :param skip: When True, the target tag is actually not entered. Typically used for XmlData and friends. :param method: One of ``(None, 'html', 'xml')``. When not ``None``, overrides the output method of lxml. :param skip_dupe: When ``False`` (the default) if this function is called repeatedly for the same tag, the tag is exited and reentered. This typically happens for types with ``max_occurs`` > 1 (eg. arrays). """ logger_c.debug("entering %s %r nsmap=%r attrib=%r skip=%s method=%s", cloth.tag, cloth.attrib, cloth.nsmap, attrib, skip, method) if not ctx.outprot_ctx.doctype_written: self.write_doctype(ctx, parent, cloth) tags = ctx.protocol.tags rootstack = ctx.protocol.rootstack assert isinstance(rootstack, oset) eltstack = ctx.protocol.eltstack ctxstack = ctx.protocol.ctxstack cureltstack = eltstack[rootstack.back] curctxstack = ctxstack[rootstack.back] if skip_dupe and len(cureltstack) > 0 and cureltstack[-1] is cloth: return cloth_root = cloth.getroottree().getroot() if not cloth_root in rootstack: rootstack.add(cloth_root) cureltstack = eltstack[rootstack.back] curctxstack = ctxstack[rootstack.back] assert rootstack.back == cloth_root while rootstack.back != cloth_root: self._close_cloth(ctx, parent) last_elt = None if len(cureltstack) > 0: last_elt = cureltstack[-1] ancestors = _revancestors(cloth) # move up in tag stack until the ancestors of both # source and target tags match while ancestors[:len(cureltstack)] != cureltstack: elt = cureltstack.pop() elt_ctx = curctxstack.pop() last_elt = elt if elt_ctx is not None: self.event_manager.fire_event(("before_exit", elt), ctx, parent) elt_ctx.__exit__(None, None, None) logger_c.debug("\texit norm %s %s", elt.tag, elt.attrib) if elt.tail is not None: parent.write(elt.tail) # unless we're at the same level as the relevant ancestor of the # target node if ancestors[:len(cureltstack)] != cureltstack: # write following siblings before closing parent node for sibl in elt.itersiblings(preceding=False): logger_c.debug("\twrite exit sibl %s %r %d", sibl.tag, sibl.attrib, id(sibl)) parent.write(sibl) # write remaining ancestors of the target node. for anc in ancestors[len(cureltstack):]: # write previous siblings of ancestors (if any) prevsibls = _prevsibls(anc, self.strip_comments, since=last_elt) for elt in prevsibls: if id(elt) in tags: logger_c.debug("\tskip anc prevsibl %s %r", elt.tag, elt.attrib) continue logger_c.debug("\twrite anc prevsibl %s %r 0x%x", elt.tag, elt.attrib, id(elt)) parent.write(elt) # enter the ancestor node kwargs = {} if len(cureltstack) == 0: # if this is the first node ever, initialize namespaces as well kwargs['nsmap'] = anc.nsmap anc_ctx = parent.element(anc.tag, anc.attrib, **kwargs) anc_ctx.__enter__() logger_c.debug("\tenter norm %s %r 0x%x method: %r", anc.tag, anc.attrib, id(anc), method) if anc.text is not None: parent.write(anc.text) rootstack.add(anc.getroottree().getroot()) cureltstack = eltstack[rootstack.back] curctxstack = ctxstack[rootstack.back] cureltstack.append(anc) curctxstack.append(anc_ctx) # now that at the same level as the target node, # write its previous siblings prevsibls = _prevsibls(cloth, self.strip_comments, since=last_elt) for elt in prevsibls: if elt is last_elt: continue if id(elt) in tags: logger_c.debug("\tskip cloth prevsibl %s %r", elt.tag, elt.attrib) continue logger_c.debug("\twrite cloth prevsibl %s %r", elt.tag, elt.attrib) parent.write(elt) skip = skip or (cloth.tag == self.DATA_TAG_NAME) if skip: tags.add(id(cloth)) if method is not None: curtag = parent.method(method) curtag.__enter__() else: curtag = None else: # finally, enter the target node. cloth_attrib = dict([(k, v) for k, v in cloth.attrib.items() if not k in self.SPYNE_ATTRS]) cloth_attrib.update(attrib) self.event_manager.fire_event(("before_entry", cloth), ctx, parent, cloth_attrib) kwargs = {} if len(cureltstack) == 0: # if this is the first node ever, initialize namespaces as well kwargs['nsmap'] = cloth.nsmap if method is not None: kwargs['method'] = method curtag = parent.element(cloth.tag, cloth_attrib, **kwargs) curtag.__enter__() if cloth.text is not None: parent.write(cloth.text) rootstack.add(cloth.getroottree().getroot()) cureltstack = eltstack[rootstack.back] curctxstack = ctxstack[rootstack.back] cureltstack.append(cloth) curctxstack.append(curtag) logger_c.debug("") def _close_cloth(self, ctx, parent): rootstack = ctx.protocol.rootstack close_until = rootstack.back cureltstack = ctx.protocol.eltstack[close_until] curctxstack = ctx.protocol.ctxstack[close_until] for elt, elt_ctx in reversed(tuple(zip(cureltstack, curctxstack))): if elt_ctx is not None: self.event_manager.fire_event(("before_exit", elt), ctx, parent) elt_ctx.__exit__(None, None, None) logger_c.debug("exit %s close", elt.tag) if elt.tail is not None: parent.write(elt.tail) for sibl in elt.itersiblings(preceding=False): logger_c.debug("write %s nextsibl", sibl.tag) parent.write(sibl) if sibl.tail is not None: parent.write(sibl.tail) if elt is close_until: logger_c.debug("closed until %r, breaking out", close_until) break del ctx.protocol.eltstack[close_until] del ctx.protocol.ctxstack[close_until] if len(rootstack) > 0: rootstack.pop() @coroutine def to_parent_cloth(self, ctx, cls, inst, cloth, parent, name, from_arr=False, **kwargs): cls_cloth = self.get_class_cloth(cls) if cls_cloth is not None: logger_c.debug("%r to object cloth", cls) cloth = cls_cloth ctx.protocol[self].rootstack.add(cloth) ret = self.to_cloth(ctx, cls, inst, cloth, parent, '') if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except (Break, StopIteration, GeneratorExit): pass @coroutine def to_root_cloth(self, ctx, cls, inst, cloth, parent, name): if len(ctx.protocol.eltstack) > 0: ctx.protocol[self].rootstack.add(cloth) cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) ret = self.start_to_parent(ctx, cls, inst, parent, name) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except (Break, StopIteration, GeneratorExit): pass # TODO: Maybe DRY this with to_parent? @coroutine def to_cloth(self, ctx, cls, inst, cloth, parent, name=None, from_arr=False, as_attr=False, as_data=False, **kwargs): prot_name = self.__class__.__name__ if issubclass(cls, XmlAttribute): cls = cls.type as_attr = True elif issubclass(cls, XmlData): cls = cls.type as_data = True pushed = False if cloth is None: logger_c.debug("No cloth fround, switching to to_parent...") ret = self.to_parent(ctx, cls, inst, parent, name, **kwargs) else: cls, _ = self.get_polymorphic_target(cls, inst) cls_attrs = self.get_cls_attrs(cls) inst = self._sanitize(cls_attrs, inst) # if instance is None, use the default factory to generate one _df = cls_attrs.default_factory if inst is None and callable(_df): inst = _df() # if instance is still None, use the default value if inst is None: inst = cls_attrs.default # if there's a subprotocol, switch to it subprot = cls_attrs.prot if subprot is not None and not (subprot is self): # we can't do this because subprotocols don't accept cloths. # so we need to enter the cloth, which make it too late to # set attributes. assert not as_attr, "No subprot supported for fields " \ "to be serialized as attributes, use type casting with " \ "customized serializers in the current protocol instead." self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method, skip=as_data) ret = subprot.subserialize(ctx, cls, inst, parent, name, as_attr=as_attr, as_data=as_data, **kwargs) # if there is no subprotocol, try rendering the value else: ret = None # try rendering the null value if inst is None: if cls_attrs.min_occurs > 0: attrs = {} if as_attr: # FIXME: test needed attrs[name] = '' self._enter_cloth(ctx, cloth, parent, attrib=attrs, method=cls_attrs.method) identifier = "%s.%s" % (prot_name, "null_to_cloth") logger_s.debug("Writing '%s' using %s type: %s.", name, identifier, cls.get_type_name()) parent.write(cloth) else: logger_s.debug("Skipping '%s' type: %s because empty.", name, cls.get_type_name()) self._enter_cloth(ctx, cloth, parent, skip=True, method=cls_attrs.method) elif as_data: # we only support XmlData of a primitive.,. is this a # problem? ret = self.to_unicode(cls, inst) if ret is not None: parent.write(ret) elif as_attr: sub_name = cls_attrs.sub_name if sub_name is None: sub_name = name attrs = {sub_name: self.to_unicode(cls, inst)} self._enter_cloth(ctx, cloth, parent, attrib=attrs, method=cls_attrs.method) else: # push the instance at hand to instance stack. this makes it # easier for protocols to make decisions based on parents of # instances at hand. pushed = True logger_c.debug("%s %r pushed %r %r", R("#"), self, cls, inst) ctx.outprot_ctx.inst_stack.append((cls, inst, from_arr)) # try rendering the array value if not from_arr and cls.Attributes.max_occurs > 1: ret = self.array_to_cloth(ctx, cls, inst, cloth, parent, as_attr=as_attr, name=name) else: # try rendering anything else handler = self.rendering_handlers[cls] # disabled for performance reasons # identifier = "%s.%s" % (prot_name, handler.__name__) # from spyne.util.web import log_repr # logger_s.debug("Writing %s using %s for %s. Inst: %r", # name, identifier, cls.get_type_name(), # log_repr(inst, cls, from_array=from_arr)) ret = handler(ctx, cls, inst, cloth, parent, name=name, as_attr=as_attr) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except (Break, StopIteration, GeneratorExit): pass finally: if pushed: logger_c.debug("%s %r popped %r %r", B("#"), self, cls, inst) ctx.outprot_ctx.inst_stack.pop() else: if pushed: logger_c.debug("%s %r popped %r %r", B("#"), self, cls, inst) ctx.outprot_ctx.inst_stack.pop() def model_base_to_cloth(self, ctx, cls, inst, cloth, parent, name, **kwargs): cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) # FIXME: Does it make sense to do this in other types? if self.WRITE_CONTENTS_WHEN_NOT_NONE in cloth.attrib: logger_c.debug("Writing contents for %r", cloth) for c in cloth: parent.write(c) else: parent.write(self.to_unicode(cls, inst)) def xml_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) if isinstance(inst, string_types): inst = etree.fromstring(inst) parent.write(inst) def any_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) parent.write(inst) def html_to_cloth(self, ctx, cls, inst, cloth, parent, name, **_): cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) if isinstance(inst, string_types): inst = html.fromstring(inst) parent.write(inst) def any_uri_to_cloth(self, ctx, cls, inst, cloth, parent, name, **kwargs): cls_attrs = self.get_cls_attrs(cls) self._enter_cloth(ctx, cloth, parent, method=cls_attrs.method) self.any_uri_to_parent(ctx, cls, inst, parent, name, **kwargs) @coroutine def complex_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, as_attr=False, **kwargs): fti = cls.get_flat_type_info(cls) cls_attrs = self.get_cls_attrs(cls) # It's actually an odict but that's irrelevant here. fti_check = dict(fti.items()) elt_check = set() attrib = self._gen_attrib_dict(inst, fti) self._enter_cloth(ctx, cloth, parent, attrib=attrib, method=cls_attrs.method) for elt in self._get_elts(cloth, self.MRPC_ID): self._actions_to_cloth(ctx, cls, inst, elt) if self._is_tagbag(cloth): logger_c.debug("%r(%r) IS a tagbag", cloth, cloth.attrib) elts = self._get_elts(cloth) else: logger_c.debug("%r(%r) is NOT a tagbag", cloth, cloth.attrib) elts = self._get_outmost_elts(cloth) # Check for xmldata after entering the cloth. as_data_field = cloth.attrib.get(self.DATA_ATTR_NAME, None) if as_data_field is not None: self._process_field(ctx, cls, inst, parent, cloth, fti, as_data_field, as_attr, True, fti_check, elt_check, **kwargs) for elt in elts: for k_attr, as_attr, as_data in ((self.ID_ATTR_NAME, False, False), (self.ATTR_ATTR_NAME, True, False), (self.DATA_ATTR_NAME, False, True)): field_name = elt.attrib.get(k_attr, None) if field_name is None: continue if elt.tag == self.DATA_TAG_NAME: as_data = True ret = self._process_field(ctx, cls, inst, parent, elt, fti, field_name, as_attr=as_attr, as_data=as_data, fti_check=fti_check, elt_check=elt_check, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass finally: # cf below if not (as_attr or as_data): break else: # this is here so that attribute on complex model doesn't get # mixed with in-line attr inside complex model. if an element # has spyne-id, all other attrs are ignored and are processed # by the object's serializer not its parent. if not (as_attr or as_data): break if len(fti_check) > 0: logger_s.debug("No element found for the following fields: %r", list(fti_check.keys())) if len(elt_check) > 0: logger_s.debug("No field found for element the following " "elements: %r", list(elt_check)) def _process_field(self, ctx, cls, inst, parent, elt, fti, field_name, as_attr, as_data, fti_check, elt_check, **kwargs): field_type = fti.get(field_name, None) fti_check.pop(field_name, None) if field_type is None: logger_c.warning("elt id %r not in %r", field_name, cls) elt_check.add(field_name) self._enter_cloth(ctx, elt, parent, skip=True) return cls_attrs = self.get_cls_attrs(field_type) if cls_attrs.exc: logger_c.debug("Skipping elt id %r because " "it was excluded", field_name) return sub_name = cls_attrs.sub_name if sub_name is None: sub_name = field_name if issubclass(cls, Array): # if cls is an array, inst should already be a sequence type # (eg list), so there's no point in doing a getattr -- we will # unwrap it and serialize it in the next round of to_cloth call. val = inst else: val = getattr(inst, field_name, None) if as_data: self._enter_cloth(ctx, elt, parent, skip=True, skip_dupe=True, method=cls_attrs.method) return self.to_cloth(ctx, field_type, val, elt, parent, name=sub_name, as_attr=as_attr, as_data=as_data, **kwargs) @coroutine def array_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, **kwargs): if isinstance(inst, PushBase): while True: sv = (yield) ret = self.to_cloth(ctx, cls, sv, cloth, parent, name=name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass else: sv = _NODATA for sv in inst: was_empty = False ret = self.to_cloth(ctx, cls, sv, cloth, parent, from_arr=True, name=name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass if sv is _NODATA: # FIXME: what if min_occurs >= 1? # fake entering the cloth to prevent it from being flushed as # parent or sibling of another node later. self._enter_cloth(ctx, cloth, parent, skip=True) spyne-spyne-2.14.0/spyne/protocol/cloth/to_parent.py000066400000000000000000000510411417664205300225560ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from spyne.util.six.moves.collections_abc import Iterable from lxml import etree, html from lxml.builder import E from spyne.const.xml import NS_XSI, NS_SOAP11_ENV, SOAP11_ENV from spyne.model import PushBase, ComplexModelBase, AnyXml, Fault, AnyDict, \ AnyHtml, ModelBase, ByteArray, XmlData, Any, AnyUri, ImageUri, XmlAttribute from spyne.model.enum import EnumBase from spyne.protocol import OutProtocolBase from spyne.protocol.xml import SchemaValidationError from spyne.util import coroutine, Break, six from spyne.util.cdict import cdict from spyne.util.etreeconv import dict_to_etree from spyne.util.color import R, B from spyne.util.six import string_types class ToParentMixin(OutProtocolBase): def __init__(self, app=None, mime_type=None, ignore_uncap=False, ignore_wrappers=False, polymorphic=True): super(ToParentMixin, self).__init__(app=app, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers) self.polymorphic = polymorphic self.use_global_null_handler = True self.serialization_handlers = cdict({ ModelBase: self.model_base_to_parent, AnyXml: self.any_xml_to_parent, AnyUri: self.any_uri_to_parent, ImageUri: self.imageuri_to_parent, AnyDict: self.any_dict_to_parent, AnyHtml: self.any_html_to_parent, Any: self.any_to_parent, Fault: self.fault_to_parent, EnumBase: self.enum_to_parent, ByteArray: self.byte_array_to_parent, ComplexModelBase: self.complex_to_parent, SchemaValidationError: self.schema_validation_error_to_parent, }) def start_to_parent(self, ctx, cls, inst, parent, name, **kwargs): """This is what subserialize calls""" # if no doctype was written, write it if not ctx.outprot_ctx.doctype_written: self.write_doctype(ctx, parent) return self.to_parent(ctx, cls, inst, parent, name, **kwargs) @staticmethod def get_subprot(ctx, cls_attrs, nosubprot=False): subprot = cls_attrs.prot if subprot is not None and not nosubprot and not \ (subprot in ctx.protocol.prot_stack): return subprot return None def to_subprot(self, ctx, cls, inst, parent, name, subprot, **kwargs): return subprot.subserialize(ctx, cls, inst, parent, name, **kwargs) @coroutine def to_parent(self, ctx, cls, inst, parent, name, nosubprot=False, **kwargs): pushed = False has_cloth = False prot_name = self.__class__.__name__ cls, switched = self.get_polymorphic_target(cls, inst) cls_attrs = self.get_cls_attrs(cls) if cls_attrs.out_type: logger.debug("out_type from %r to %r", cls, cls_attrs.out_type) cls = cls_attrs.out_type cls_attrs = self.get_cls_attrs(cls) inst = self._sanitize(cls_attrs, inst) # if there is a subprotocol, switch to it subprot = self.get_subprot(ctx, cls_attrs, nosubprot) if subprot is not None: logger.debug("Subprot from %r to %r", self, subprot) ret = self.to_subprot(ctx, cls, inst, parent, name, subprot, **kwargs) else: # if there is a class cloth, switch to it has_cloth, cor_handle = self.check_class_cloths(ctx, cls, inst, parent, name, **kwargs) if has_cloth: ret = cor_handle else: # if instance is None, use the default factory to generate one _df = cls_attrs.default_factory if inst is None and callable(_df): inst = _df() # if instance is still None, use the default value if inst is None: inst = cls_attrs.default # if instance is still None, use the global null handler to # serialize it if inst is None and self.use_global_null_handler: identifier = prot_name + '.null_to_parent' logger.debug("Writing %s using %s for %s.", name, identifier, cls.get_type_name()) self.null_to_parent(ctx, cls, inst, parent, name, **kwargs) return # if requested, ignore wrappers if self.ignore_wrappers and issubclass(cls, ComplexModelBase): cls, inst = self.strip_wrappers(cls, inst) # if cls is an iterable of values and it's not being iterated # on, do it from_arr = kwargs.get('from_arr', False) # we need cls.Attributes here because we need the ACTUAL attrs # that were set by the Array.__new__ if not from_arr and cls.Attributes.max_occurs > 1: ret = self.array_to_parent(ctx, cls, inst, parent, name, **kwargs) else: # fetch the serializer for the class at hand try: handler = self.serialization_handlers[cls] except KeyError: # if this protocol uncapable of serializing this class if self.ignore_uncap: logger.debug("Ignore uncap %r", name) return # ignore it if requested # raise the error otherwise logger.error("%r is missing handler for " "%r for field %r", self, cls, name) raise # push the instance at hand to instance stack. this makes it # easier for protocols to make decisions based on parents # of instances at hand. ctx.outprot_ctx.inst_stack.append( (cls, inst, from_arr) ) pushed = True logger.debug("%s %r pushed %r using %r", R("$"), self, cls, handler) # disabled for performance reasons # from spyne.util.web import log_repr # identifier = "%s.%s" % (prot_name, handler.__name__) # log_str = log_repr(inst, cls, # from_array=kwargs.get('from_arr', None)) # logger.debug("Writing %s using %s for %s. Inst: %r", name, # identifier, cls.get_type_name(), log_str) # finally, serialize the value. ret is the coroutine handle ret = handler(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except (Break, StopIteration, GeneratorExit): pass finally: if has_cloth: self._close_cloth(ctx, parent) if pushed: logger.debug("%s %r popped %r %r", B("$"), self, cls, inst) ctx.outprot_ctx.inst_stack.pop() else: if has_cloth: self._close_cloth(ctx, parent) if pushed: logger.debug("%s %r popped %r %r", B("$"), self, cls, inst) ctx.outprot_ctx.inst_stack.pop() @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): if inst is None: inst = () ser_subprot = self.get_subprot(ctx, self.get_cls_attrs(cls)) # FIXME: it's sad that this function has the same code twice. if isinstance(inst, PushBase): # this will be popped by pusher_try_close ctx.pusher_stack.append(inst) i = 0 try: while True: sv = (yield) # disabled because to_parent is supposed to take care of this #ctx.protocol.inst_stack.append((cls, sv, True)) kwargs['from_arr'] = True kwargs['array_index'] = i if ser_subprot is not None: ser_subprot.column_table_before_row(ctx, cls, inst, parent, name, **kwargs) ret = self.to_parent(ctx, cls, sv, parent, name, **kwargs) i += 1 if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass finally: # disabled because to_parent is supposed to take care of this #popped_val = ctx.protocol.inst_stack.pop() #assert popped_val is sv if ser_subprot is not None: ser_subprot.column_table_before_row(ctx, cls, inst, parent, name, **kwargs) else: # disabled because to_parent is supposed to take care of this #popped_val = ctx.protocol.inst_stack.pop() #assert popped_val is sv if ser_subprot is not None: ser_subprot.column_table_after_row(ctx, cls, inst, parent, name, **kwargs) except Break: # pusher is done with pushing pass else: assert isinstance(inst, Iterable), ("%r is not iterable" % (inst,)) for i, sv in enumerate(inst): # disabled because to_parent is supposed to take care of this #ctx.protocol.inst_stack.append((cls, sv, True) kwargs['from_arr'] = True kwargs['array_index'] = i if ser_subprot is not None: ser_subprot.column_table_before_row(ctx, cls, inst, parent, name, **kwargs) ret = self.to_parent(ctx, cls, sv, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass finally: # disabled because to_parent is supposed to take care of this #popped_val = ctx.protocol.inst_stack.pop() #assert popped_val is sv if ser_subprot is not None: ser_subprot.column_table_after_row(ctx, cls, inst, parent, name, **kwargs) else: # disabled because to_parent is supposed to take care of this #popped_val = ctx.protocol.inst_stack.pop() #assert popped_val is sv if ser_subprot is not None: ser_subprot.column_table_after_row(ctx, cls, inst, parent, name, **kwargs) def not_supported(self, ctx, cls, *args, **kwargs): if not self.ignore_uncap: raise NotImplementedError("Serializing %r not supported!" % cls) def any_uri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): self.model_base_to_parent(ctx, cls, inst, parent, name, **kwargs) def imageuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): self.model_base_to_parent(ctx, cls, inst, parent, name, **kwargs) def byte_array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, self.to_unicode(cls, inst, self.binary_encoding))) def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, self.to_unicode(cls, inst))) def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, **{'{%s}nil' % NS_XSI: 'true'})) def enum_to_parent(self, ctx, cls, inst, parent, name, **kwargs): self.model_base_to_parent(ctx, cls, str(inst), parent, name) def any_xml_to_parent(self, ctx, cls, inst, parent, name, **kwargs): if isinstance(inst, string_types): inst = etree.fromstring(inst) parent.write(E(name, inst)) def any_html_to_unicode(self, cls, inst, **_): if isinstance(inst, (str, six.text_type)): inst = html.fromstring(inst) return inst def any_html_to_parent(self, ctx, cls, inst, parent, name, **kwargs): cls_attrs = self.get_cls_attrs(cls) if cls_attrs.as_string: if not (isinstance(inst, str) or isinstance(inst, six.text_type)): inst = html.tostring(inst) else: if isinstance(inst, str) or isinstance(inst, six.text_type): inst = html.fromstring(inst) parent.write(E(name, inst)) def any_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E(name, inst)) def any_dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): elt = E(name) dict_to_etree(inst, elt) parent.write(E(name, elt)) def _gen_sub_name(self, cls, cls_attrs, k, use_ns=None): if self.use_ns is not None and use_ns is None: use_ns = self.use_ns sub_ns = cls_attrs.sub_ns if sub_ns is None: sub_ns = cls.get_namespace() sub_name = cls_attrs.sub_name if sub_name is None: sub_name = k if use_ns: name = "{%s}%s" % (sub_ns, sub_name) else: name = sub_name return name @coroutine def _write_members(self, ctx, cls, inst, parent, use_ns=None, **kwargs): if self.use_ns is not None and use_ns is None: use_ns = self.use_ns for k, v in self.sort_fields(cls): attr = self.get_cls_attrs(v) if attr.exc: prot_name = self.__class__.__name__ logger.debug("%s: excluded for %s.", k, prot_name) continue if issubclass(v, XmlAttribute): continue try: # e.g. SqlAlchemy could throw NoSuchColumnError subvalue = getattr(inst, k, None) except: subvalue = None # This is a tight loop, so enable this only when necessary. # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) sub_name = self._gen_sub_name(cls, attr, k, use_ns) if issubclass(v, XmlData): if issubclass(v.type, AnyXml): parent.write(subvalue) else: subvalstr = self.to_unicode(v.type, subvalue) if subvalstr is not None: parent.write(subvalstr) continue if subvalue is not None or attr.min_occurs > 0: ret = self.to_parent(ctx, v, subvalue, parent, sub_name, use_ns=use_ns, **kwargs) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass @coroutine def _complex_to_parent_do(self, ctx, cls, inst, parent, **kwargs): # parent.write(u"\u200c") # zero-width non-joiner parent.write(" ") # FIXME: to force empty tags to be sent as # instead of ret = self._write_members(ctx, cls, inst, parent, **kwargs) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass def complex_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, use_ns=None, **kwargs): if not from_arr: inst = cls.get_serialization_instance(inst) attrib = self._gen_attrib_dict(inst, cls.get_flat_type_info(cls)) if self.skip_root_tag: self._complex_to_parent_do(ctx, cls, inst, parent, from_arr=from_arr, **kwargs) else: if name is None or name == '': name = self._gen_sub_name(cls, self.get_cls_attrs(cls), cls.get_type_name(), use_ns) logger.debug("name is empty, long live name: %s, cls: %r", name, cls) with parent.element(name, attrib=attrib): self._complex_to_parent_do(ctx, cls, inst, parent, from_arr=from_arr, **kwargs) def fault_to_parent(self, ctx, cls, inst, parent, name): PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP11_ENV] tag_name = SOAP11_ENV("Fault") with parent.element(tag_name): parent.write( E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), E("faultstring", inst.faultstring), E("faultactor", inst.faultactor), ) if isinstance(inst.detail, etree._Element): parent.write(E.detail(inst.detail)) # add other nonstandard fault subelements with get_members_etree self._write_members(ctx, cls, inst, parent) # no need to track the returned generator because we expect no # PushBase instance here. def schema_validation_error_to_parent(self, ctx, cls, inst, parent, **_): PREF_SOAP_ENV = ctx.app.interface.prefmap[NS_SOAP11_ENV] tag_name = SOAP11_ENV("Fault") with parent.element(tag_name): parent.write( E("faultcode", '%s:%s' % (PREF_SOAP_ENV, inst.faultcode)), # HACK: Does anyone know a better way of injecting raw xml entities? E("faultstring", html.fromstring(inst.faultstring).text), E("faultactor", inst.faultactor), ) if isinstance(inst.detail, etree._Element): parent.write(E.detail(inst.detail)) # add other nonstandard fault subelements with get_members_etree self._write_members(ctx, cls, inst, parent) # no need to track the returned generator because we expect no # PushBase instance here. spyne-spyne-2.14.0/spyne/protocol/csv.py000066400000000000000000000105541417664205300202510ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.csv`` package contains the Csv output protocol. This protocol is here merely for illustration purposes. While it is in a somewhat working state, it is not that easy to use. Expect a revamp in the coming versions. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import csv from spyne import ComplexModelBase from spyne.util import six from spyne.protocol.dictdoc import HierDictDocument if six.PY2: from StringIO import StringIO else: from io import StringIO def _complex_to_csv(prot, ctx): cls, = ctx.descriptor.out_message._type_info.values() queue = StringIO() serializer, = cls._type_info.values() if issubclass(serializer, ComplexModelBase): type_info = serializer.get_flat_type_info(serializer) keys = [k for k, _ in prot.sort_fields(serializer)] else: type_info = {serializer.get_type_name(): serializer} keys = list(type_info.keys()) if ctx.out_error is not None: writer = csv.writer(queue, dialect=csv.excel) writer.writerow(['Error in generating the document']) if ctx.out_error is not None: for r in ctx.out_error.to_bytes_iterable(ctx.out_error): writer.writerow([r]) yield queue.getvalue() queue.truncate(0) else: writer = csv.DictWriter(queue, dialect=csv.excel, fieldnames=keys) if prot.header: titles = {} for k in keys: v = type_info[k] titles[k] = prot.trc(v, ctx.locale, k) writer.writerow(titles) yield queue.getvalue() queue.truncate(0) if ctx.out_object[0] is not None: for v in ctx.out_object[0]: d = prot._to_dict_value(serializer, v, set()) if six.PY2: for k in d: if isinstance(d[k], unicode): d[k] = d[k].encode('utf8') writer.writerow(d) yval = queue.getvalue() yield yval queue.truncate(0) class Csv(HierDictDocument): mime_type = 'text/csv' text_based = True type = set(HierDictDocument.type) type.add('csv') def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=True, complex_as=dict, ordered=False, polymorphic=False, header=True): super(Csv, self).__init__(app=app, validator=validator, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, complex_as=complex_as, ordered=ordered, polymorphic=polymorphic) self.header = header def create_in_document(self, ctx): raise NotImplementedError() def serialize(self, ctx, message): assert message in (self.RESPONSE, ) if ctx.out_object is None: ctx.out_object = [] assert len(ctx.descriptor.out_message._type_info) == 1, \ "CSV Serializer supports functions with exactly one return type: " \ "%r" % ctx.descriptor.out_message._type_info def create_out_string(self, ctx): ctx.out_string = _complex_to_csv(self, ctx) if 'http' in ctx.transport.type: ctx.transport.resp_headers['Content-Disposition'] = ( 'attachment; filename=%s.csv;' % ctx.descriptor.name) def any_uri_to_unicode(self, cls, value, **_): if isinstance(value, cls.Value): value = value.text return super(Csv, self).any_uri_to_unicode(cls, value, **_) spyne-spyne-2.14.0/spyne/protocol/dictdoc/000077500000000000000000000000001417664205300205105ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/dictdoc/__init__.py000066400000000000000000000075031417664205300226260ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.dictdoc`` module contains an abstract protocol that deals with hierarchical and flat dicts as {in,out}_documents. Flattening ========== Plain HTTP does not support hierarchical key-value stores. Spyne makes plain HTTP fake hierarchical dicts with two small hacks. Let's look at the following object hierarchy: :: class Inner(ComplexModel): c = Integer d = Array(Integer) class Outer(ComplexModel): a = Integer b = Inner For example, the ``Outer(a=1, b=Inner(c=2))`` object would correspond to the following hierarchichal dict representation: :: {'a': 1, 'b': { 'c': 2 }} Here's what we do to deserialize the above object structure from a flat dict: 1. Object hierarchies are flattened. e.g. the flat representation of the above dict is: ``{'a': 1, 'b.c': 2}``. 2. Arrays of objects are sent using variables with array indexes in square brackets. So the request with the following query object: :: {'a': 1, 'b.d[0]': 1, 'b.d[1]': 2}} ... corresponds to: :: {'a': 1, 'b': { 'd': [1,2] }} If we had: :: class Inner(ComplexModel): c = Integer class Outer(ComplexModel): a = Integer b = Array(SomeObject) Or the following object: :: {'a': 1, 'b[0].c': 1, 'b[1].c': 2}} ... would correspond to: :: {'a': 1, 'b': [{ 'c': 1}, {'c': 2}]} ... which would deserialize as: :: Outer(a=1, b=[Inner(c=1), Inner(c=2)]) These hacks are both slower to process and bulkier on wire, so use class hierarchies with HTTP only when performance is not that much of a concern. Cookies ======= Cookie headers are parsed and fields within HTTP requests are assigned to fields in the ``in_header`` class, if defined. It's also possible to get the ``Cookie`` header intact by defining an ``in_header`` object with a field named ``Cookie`` (case sensitive). As an example, let's assume the following HTTP request: :: GET / HTTP/1.0 Cookie: v1=4;v2=8 (...) The keys ``v1`` and ``v2`` are passed to the instance of the ``in_header`` class if it has fields named ``v1`` or ``v2``\\. Wrappers ======== Wrapper objects are an artifact of the Xml world, which don't really make sense in other protocols. Let's look at the following object: :: v = Permission(application='app', feature='f1'), Here's how it would be serialized to XML: :: app f1 With ``ignore_wrappers=True`` (which is the default) This gets serialized to dict as follows: :: { "application": "app", "feature": "f1" } When ``ignore_wrappers=False``, the same value/type combination would result in the following dict: :: {"Permission": { { "application": "app", "feature": "f1" } }, This could come in handy in case you don't know what type to expect. """ from spyne.protocol.dictdoc._base import DictDocument from spyne.protocol.dictdoc.hier import HierDictDocument from spyne.protocol.dictdoc.simple import SimpleDictDocument spyne-spyne-2.14.0/spyne/protocol/dictdoc/_base.py000066400000000000000000000117621417664205300221420ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) import re RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") from spyne.error import ValidationError from spyne.model import Fault, Array, AnyXml, AnyHtml, Uuid, DateTime, Date, \ Time, Duration from spyne.protocol import ProtocolBase class DictDocument(ProtocolBase): """An abstract protocol that can use hierarchical or flat dicts as input and output documents. Implement ``serialize()``, ``deserialize()``, ``create_in_document()`` and ``create_out_string()`` to use this. """ # flags to be used in tests _decimal_as_string = False _huge_numbers_as_string = False text_based = False def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=True, complex_as=dict, ordered=False, polymorphic=False, key_encoding=None): super(DictDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers) self.key_encoding = key_encoding self.polymorphic = polymorphic self.complex_as = complex_as self.ordered = ordered if ordered: raise NotImplementedError('ordered=True') self.stringified_types = (DateTime, Date, Time, Uuid, Duration, AnyXml, AnyHtml) def set_validator(self, validator): """Sets the validator for the protocol. :param validator: one of ('soft', None) """ if validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: self.validator = None else: raise ValueError(validator) def decompose_incoming_envelope(self, ctx, message): """Sets ``ctx.in_body_doc``, ``ctx.in_header_doc`` and ``ctx.method_request_string`` using ``ctx.in_document``. """ assert message in (ProtocolBase.REQUEST, ProtocolBase.RESPONSE) # set ctx.in_header ctx.transport.in_header_doc = None # use an rpc protocol if you want headers. doc = ctx.in_document ctx.in_header_doc = None ctx.in_body_doc = doc if message is ProtocolBase.REQUEST: #logger.debug('\theader : %r', ctx.in_header_doc) #logger.debug('\tbody : %r', ctx.in_body_doc) if not isinstance(doc, dict) or len(doc) != 1: raise ValidationError(doc, "Need a dictionary with exactly one key as method name.") if len(doc) == 0: raise Fault("Client", "Empty request") ctx.method_request_string = self.gen_method_request_string(ctx) def gen_method_request_string(self, ctx): """Uses information in context object to return a method_request_string. Returns a string in the form of "{namespaces}method name". """ mrs, = ctx.in_body_doc.keys() return '{%s}%s' % (self.app.interface.get_tns(), mrs) def deserialize(self, ctx, message): raise NotImplementedError() def serialize(self, ctx, message): raise NotImplementedError() def create_in_document(self, ctx, in_string_encoding=None): raise NotImplementedError() def create_out_string(self, ctx, out_string_encoding='utf8'): raise NotImplementedError() def _check_freq_dict(self, cls, d, fti=None): if fti is None: fti = cls.get_flat_type_info(cls) for k, v in fti.items(): val = d[k] attrs = self.get_cls_attrs(v) min_o, max_o = attrs.min_occurs, attrs.max_occurs if issubclass(v, Array) and v.Attributes.max_occurs == 1: v, = v._type_info.values() attrs = self.get_cls_attrs(v) min_o, max_o = attrs.min_occurs, attrs.max_occurs if val < min_o: raise ValidationError("%r.%s" % (cls, k), '%%s member must occur at least %d times.' % min_o) elif val > max_o: raise ValidationError("%r.%s" % (cls, k), '%%s member must occur at most %d times.' % max_o) spyne-spyne-2.14.0/spyne/protocol/dictdoc/hier.py000066400000000000000000000501761417664205300220220ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) import re RE_HTTP_ARRAY_INDEX = re.compile("\\[([0-9]+)\\]") from mmap import mmap from collections import defaultdict from spyne.util import six from spyne.util.six.moves.collections_abc import Iterable as AbcIterable from spyne.error import ValidationError from spyne.error import ResourceNotFoundError from spyne.model import ByteArray, File, Fault, ComplexModelBase, Array, Any, \ AnyDict, Uuid, Unicode from spyne.protocol.dictdoc import DictDocument class HierDictDocument(DictDocument): """This protocol contains logic for protocols that serialize and deserialize hierarchical dictionaries. Examples include: Json, MessagePack and Yaml. Implement ``create_in_document()`` and ``create_out_string()`` to use this. """ VALID_UNICODE_SOURCES = (six.text_type, six.binary_type, memoryview, mmap, bytearray) from_serstr = DictDocument.from_unicode to_serstr = DictDocument.to_unicode def get_class_name(self, cls): class_name = cls.get_type_name() if not six.PY2: if isinstance(class_name, bytes): class_name = class_name.decode('utf8') return class_name def get_complex_as(self, attr): if attr.complex_as is None: return self.complex_as return attr.complex_as def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) # instantiate the result message if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message else: raise ValueError(message) # should be impossible if body_class: # assign raw result to its wrapper, result_message doc = ctx.in_body_doc logger.debug("Request: %r", doc) class_name = self.get_class_name(body_class) if self.ignore_wrappers: doc = doc.get(class_name, None) result_message = self._doc_to_object(ctx, body_class, doc, self.validator) ctx.in_object = result_message else: ctx.in_object = [] self.event_manager.fire_event('after_deserialize', ctx) def _fault_to_doc(self, inst, cls=None): if cls is None: cls = Fault if self.complex_as is list: return [cls.to_list(inst.__class__, inst, self)] elif self.complex_as is tuple: fault_as_list = [Fault.to_list(inst.__class__, inst, self)] return tuple(fault_as_list) else: return [Fault.to_dict(inst.__class__, inst, self)] def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: ctx.out_document = self._fault_to_doc(ctx.out_error) return # get the result message if message is self.REQUEST: out_type = ctx.descriptor.in_message elif message is self.RESPONSE: out_type = ctx.descriptor.out_message else: assert False if out_type is None: return # assign raw result to its wrapper, result_message if ctx.descriptor.is_out_bare(): out_instance, = ctx.out_object else: out_type_info = out_type.get_flat_type_info(out_type) # instantiate the result message out_instance = out_type() for i, (k, v) in enumerate(out_type_info.items()): attrs = self.get_cls_attrs(v) out_instance._safe_set(k, ctx.out_object[i], v, attrs) ctx.out_document = self._object_to_doc(out_type, out_instance, set()), logger.debug("Response: %r", ctx.out_document) self.event_manager.fire_event('after_serialize', ctx) def validate(self, key, cls, inst): if inst is None and self.get_cls_attrs(cls).nullable: pass elif issubclass(cls, Unicode) and not isinstance(inst, self.VALID_UNICODE_SOURCES): raise ValidationError([key, inst]) def _from_dict_value(self, ctx, key, cls, inst, validator): if validator is self.SOFT_VALIDATION: self.validate(key, cls, inst) cls_attrs = self.get_cls_attrs(cls) complex_as = self.get_complex_as(cls_attrs) if complex_as is list or complex_as is tuple: check_complex_as = (list, tuple) else: check_complex_as = complex_as # get native type if issubclass(cls, File): if isinstance(inst, check_complex_as): cls = cls_attrs.type or cls inst = self._parse(cls_attrs, inst) retval = self._doc_to_object(ctx, cls, inst, validator) else: retval = self.from_serstr(cls, inst, self.binary_encoding) else: inst = self._parse(cls_attrs, inst) if issubclass(cls, (Any, AnyDict)): retval = inst elif issubclass(cls, ComplexModelBase): retval = self._doc_to_object(ctx, cls, inst, validator) else: if cls_attrs.empty_is_none and inst in (u'', b''): inst = None if (validator is self.SOFT_VALIDATION and isinstance(inst, six.string_types) and not cls.validate_string(cls, inst)): raise ValidationError([key, inst]) if issubclass(cls, (ByteArray, Uuid)): retval = self.from_serstr(cls, inst, self.binary_encoding) elif issubclass(cls, Unicode): if isinstance(inst, bytearray): retval = six.text_type(inst, encoding=cls_attrs.encoding or 'ascii', errors=cls_attrs.unicode_errors) elif isinstance(inst, memoryview): # FIXME: memoryview needs a .decode() function to avoid # needless copying here retval = inst.tobytes().decode( cls_attrs.encoding or 'ascii', errors=cls_attrs.unicode_errors) elif isinstance(inst, mmap): # FIXME: mmap needs a .decode() function to avoid # needless copying here retval = mmap[:].decode(cls_attrs.encoding, errors=cls_attrs.unicode_errors) elif isinstance(inst, six.binary_type): retval = self.unicode_from_bytes(cls, inst) else: retval = inst else: retval = self.from_serstr(cls, inst) # validate native type if validator is self.SOFT_VALIDATION: if not cls.validate_native(cls, retval): raise ValidationError([key, retval]) return retval def _doc_to_object(self, ctx, cls, doc, validator=None): if doc is None: return [] if issubclass(cls, Any): doc = self._cast(self.get_cls_attrs(cls), doc) return doc if issubclass(cls, Array): doc = self._cast(self.get_cls_attrs(cls), doc) retval = [] (serializer,) = cls._type_info.values() if not isinstance(doc, AbcIterable): raise ValidationError(doc) for i, child in enumerate(doc): retval.append(self._from_dict_value(ctx, i, serializer, child, validator)) return retval cls_attrs = self.get_cls_attrs(cls) if not self.ignore_wrappers and not cls_attrs.not_wrapped: if not isinstance(doc, dict): raise ValidationError(doc, "Wrapper documents must be dicts") if len(doc) == 0: return None if len(doc) > 1: raise ValidationError(doc, "There can be only one entry in a " "wrapper dict") subclasses = cls.get_subclasses() (class_name, doc), = doc.items() if not six.PY2 and isinstance(class_name, bytes): class_name = class_name.decode('utf8') if cls.get_type_name() != class_name and subclasses is not None \ and len(subclasses) > 0: for subcls in subclasses: if subcls.get_type_name() == class_name: break else: raise ValidationError(class_name, "Class name %%r is not registered as a subclass of %r" % cls.get_type_name()) if not self.issubclass(subcls, cls): raise ValidationError(class_name, "Class name %%r is not a subclass of %r" % cls.get_type_name()) cls = subcls inst = cls.get_deserialization_instance(ctx) # get all class attributes, including the ones coming from # parent classes. flat_type_info = cls.get_flat_type_info(cls) if flat_type_info is None: logger.critical("No flat_type_info found for type %r", cls) raise TypeError(cls) # this is for validating cls.Attributes.{min,max}_occurs frequencies = defaultdict(int) try: items = doc.items() except AttributeError: # Input is not a dict, so we assume it's a sequence that we can pair # with the incoming sequence with field names. # TODO: cache this try: items = zip([k for k, v in flat_type_info.items() if not self.get_cls_attrs(v).exc], doc) except TypeError as e: logger.error("Invalid document %r for %r", doc, cls) raise ValidationError(doc) # parse input to set incoming data to related attributes. for k, v in items: if self.key_encoding is not None and isinstance(k, bytes): try: k = k.decode(self.key_encoding) except UnicodeDecodeError: raise ValidationError(k) member = flat_type_info.get(k, None) if member is None: member, k = flat_type_info.alt.get(k, (None, k)) if member is None: continue member_attrs = self.get_cls_attrs(member) if member_attrs.exc: continue mo = member_attrs.max_occurs if mo > 1: subinst = getattr(inst, k, None) if subinst is None: subinst = [] for a in v: subinst.append( self._from_dict_value(ctx, k, member, a, validator)) else: subinst = self._from_dict_value(ctx, k, member, v, validator) inst._safe_set(k, subinst, member, member_attrs) frequencies[k] += 1 attrs = self.get_cls_attrs(cls) if validator is self.SOFT_VALIDATION and attrs.validate_freq: self._check_freq_dict(cls, frequencies, flat_type_info) return inst def _object_to_doc(self, cls, inst, tags=None): if inst is None: return None if tags is None: tags = set() retval = None if isinstance(inst, Fault): retval = None inst_id = id(inst) if not (inst_id in tags): retval = self._fault_to_doc(inst, cls) tags.add(inst_id) return retval cls_attrs = self.get_cls_attrs(cls) if cls_attrs.exc: return cls_orig = None if cls_attrs.out_type is not None: cls_orig = cls cls = cls_attrs.out_type # remember to do this if cls_attrs are needed below # (currently cls_attrs is not used so we don't do this) # cls_attrs = self.get_cls_attrs(cls) elif cls_attrs.type is not None: cls_orig = cls cls = cls_attrs.type # remember to do this if cls_attrs are needed below # (currently cls_attrs is not used so we don't do this) # cls_attrs = self.get_cls_attrs(cls) if self.ignore_wrappers: ti = getattr(cls, '_type_info', {}) while cls.Attributes._wrapper and len(ti) == 1: # Wrappers are auto-generated objects that have exactly one # child type. key, = ti.keys() if not issubclass(cls, Array): inst = getattr(inst, key, None) cls, = ti.values() ti = getattr(cls, '_type_info', {}) # transform the results into a dict: if cls.Attributes.max_occurs > 1: if inst is not None: retval = [] for subinst in inst: if id(subinst) in tags: # even when there is ONE already-serialized instance, # we throw the whole thing away. logger.debug("Throwing the whole array away because " "found %d", id(subinst)) # this is DANGEROUS #logger.debug("Said array: %r", inst) return None retval.append(self._to_dict_value(cls, subinst, tags, cls_orig=cls_orig or cls)) else: retval = self._to_dict_value(cls, inst, tags, cls_orig=cls_orig or cls) return retval def _get_member_pairs(self, cls, inst, tags): old_len = len(tags) tags = tags | {id(inst)} assert len(tags) > old_len, ("Offending instance: %r" % inst) for k, v in self.sort_fields(cls): subattr = self.get_cls_attrs(v) if subattr.exc: continue try: subinst = getattr(inst, k, None) # to guard against e.g. sqlalchemy throwing NoSuchColumnError except Exception as e: logger.error("Error getting %r: %r" % (k, e)) subinst = None if subinst is None: subinst = subattr.default else: if id(subinst) in tags: continue logger.debug("%s%r type is %r", " " * len(tags), k, v) val = self._object_to_doc(v, subinst, tags) min_o = subattr.min_occurs complex_as = self.get_complex_as(subattr) if val is not None or min_o > 0 or complex_as is list: sub_name = subattr.sub_name if sub_name is None: sub_name = k yield (sub_name, val) def _to_dict_value(self, cls, inst, tags, cls_orig=None): if cls_orig is None: cls_orig = cls cls, switched = self.get_polymorphic_target(cls, inst) cls_attrs = self.get_cls_attrs(cls) inst = self._sanitize(cls_attrs, inst) if issubclass(cls_orig, File): cls_orig_attrs = self.get_cls_attrs(cls_orig) if not isinstance(inst, cls_orig_attrs.type): return self.to_serstr(cls_orig, inst, self.binary_encoding) retval = self._complex_to_doc(cls_orig_attrs.type, inst, tags) complex_as = self.get_complex_as(cls_orig_attrs) if complex_as is dict and not self.ignore_wrappers: retval = next(iter(retval.values())) return retval if issubclass(cls, (Any, AnyDict)): return inst if issubclass(cls, Array): st, = cls._type_info.values() return self._object_to_doc(st, inst, tags) if issubclass(cls, ComplexModelBase): return self._complex_to_doc(cls, inst, tags) if issubclass(cls, (ByteArray, Uuid)): return self.to_serstr(cls, inst, self.binary_encoding) return self.to_serstr(cls, inst) def _complex_to_doc(self, cls, inst, tags): cls_attrs = self.get_cls_attrs(cls) sf = cls_attrs.simple_field if sf is not None: # we want this to throw when sf does not exist subcls = cls.get_flat_type_info(cls)[sf] subinst = getattr(inst, sf, None) logger.debug("Render complex object %s to the value %r of its " "field '%s'", cls.get_type_name(), subinst, sf) return self.to_unicode(subcls, subinst) cls_attr = self.get_cls_attrs(cls) complex_as = self.get_complex_as(cls_attr) if complex_as is list or \ getattr(cls.Attributes, 'serialize_as', False) is list: return list(self._complex_to_list(cls, inst, tags)) return self._complex_to_dict(cls, inst, tags) def _complex_to_dict(self, cls, inst, tags): inst = cls.get_serialization_instance(inst) cls_attr = self.get_cls_attrs(cls) complex_as = self.get_complex_as(cls_attr) if self.key_encoding is None: d = complex_as(self._get_member_pairs(cls, inst, tags)) if (self.ignore_wrappers or cls_attr.not_wrapped) \ and not bool(cls_attr.wrapper): return d else: if isinstance(cls_attr.wrapper, (six.text_type, six.binary_type)): return {cls_attr.wrapper: d} else: return {cls.get_type_name(): d} else: d = complex_as( (k.encode(self.key_encoding), v) for k, v in self._get_member_pairs(cls, inst, tags) ) if (self.ignore_wrappers or cls_attr.not_wrapped) \ and not bool(cls_attr.wrapper): return d else: if isinstance(cls_attr.wrapper, six.text_type): return {cls_attr.wrapper.encode(self.key_encoding): d} elif isinstance(cls_attr.wrapper, six.binary_type): return {cls_attr.wrapper: d} else: return {cls.get_type_name().encode(self.key_encoding): d} def _complex_to_list(self, cls, inst, tags): inst = cls.get_serialization_instance(inst) for k, v in self._get_member_pairs(cls, inst, tags): yield v spyne-spyne-2.14.0/spyne/protocol/dictdoc/simple.py000066400000000000000000000364351417664205300223660ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) import re from collections import deque from collections import defaultdict from spyne.util import six from spyne.error import ValidationError from spyne.model import ByteArray, String, File, ComplexModelBase, Array, \ SimpleModel, Any, AnyDict, Unicode from spyne.protocol.dictdoc import DictDocument RE_HTTP_ARRAY_INDEX = re.compile(r"\[([0-9]+)]") def _s2cmi(m, nidx): """ Sparse to contiguous mapping inserter. >>> m1={3:0, 4:1, 7:2} >>> _s2cmi(m1, 5); m1 1 {3: 0, 4: 1, 5: 2, 7: 3} >>> _s2cmi(m1, 0); m1 0 {0: 0, 3: 1, 4: 2, 5: 3, 7: 4} >>> _s2cmi(m1, 8); m1 4 {0: 0, 3: 1, 4: 2, 5: 3, 7: 4, 8: 5} """ nv = -1 for i, v in m.items(): if i >= nidx: m[i] += 1 elif v > nv: nv = v m[nidx] = nv + 1 return nv + 1 def _fill(inst_class, frequencies): """This function initializes the frequencies dict with null values. If this is not done, it won't be possible to catch missing elements when validating the incoming document. """ ctype_info = inst_class.get_flat_type_info(inst_class) cfreq_key = inst_class, 0 for k, v in ctype_info.items(): if v.Attributes.min_occurs > 0: frequencies[cfreq_key][k] = 0 class SimpleDictDocument(DictDocument): """This protocol contains logic for protocols that serialize and deserialize flat dictionaries. The only example as of now is Http. """ def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, ignore_wrappers=True, complex_as=dict, ordered=False, hier_delim='.', strict_arrays=False): super(SimpleDictDocument, self).__init__(app=app, validator=validator, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, complex_as=complex_as, ordered=ordered) self.hier_delim = hier_delim self.strict_arrays = strict_arrays def _to_native_values(self, cls, member, orig_k, k, v, req_enc, validator): value = [] for v2 in v: # some wsgi implementations pass unicode strings, some pass str # strings. we get unicode here when we can and should. if v2 is not None and req_enc is not None \ and not issubclass(member.type, String) \ and issubclass(member.type, Unicode) \ and not isinstance(v2, six.text_type): try: v2 = v2.decode(req_enc) except UnicodeDecodeError as e: raise ValidationError(v2, "%r while decoding %%r" % e) # validate raw data (before deserialization) try: if (validator is self.SOFT_VALIDATION and not member.type.validate_string(member.type, v2)): raise ValidationError([orig_k, v2]) except TypeError: raise ValidationError([orig_k, v2]) cls_attrs = self.get_cls_attrs(member.type) v2 = self._parse(cls_attrs, v2) # deserialize to native type if issubclass(member.type, File): if isinstance(v2, File.Value): native_v2 = v2 else: native_v2 = self.from_unicode(member.type, v2, self.binary_encoding) elif issubclass(member.type, ByteArray): native_v2 = self.from_unicode(member.type, v2, self.binary_encoding) else: try: native_v2 = self.from_unicode(member.type, v2) except ValidationError as e: ns = "%s.%s" % (cls.get_namespace(), cls.get_type_name()) raise ValidationError(e.faultstring, "Validation failed for %s.%s: %%s" % (ns, k)) # validate native data (after deserialization) native_v2 = self._sanitize(cls_attrs, native_v2) if validator is self.SOFT_VALIDATION: if not member.type.validate_native(member.type, native_v2): raise ValidationError([orig_k, v2]) value.append(native_v2) return value def simple_dict_to_object(self, ctx, doc, cls, validator=None, req_enc=None): """Converts a flat dict to a native python object. See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. """ if issubclass(cls, (Any, AnyDict)): return doc if not issubclass(cls, ComplexModelBase): raise NotImplementedError("Interestingly, deserializing non complex" " types is not yet implemented. You can" " use a ComplexModel to wrap that field." " Otherwise, patches are welcome.") # this is for validating cls.Attributes.{min,max}_occurs frequencies = defaultdict(lambda: defaultdict(int)) if validator is self.SOFT_VALIDATION: _fill(cls, frequencies) if issubclass(cls, Array): # we need the wrapper object instance here as it's a root object retval = cls.get_serialization_instance([]) else: retval = cls.get_deserialization_instance(ctx) simple_type_info = cls.get_simple_type_info_with_prot(cls, self, hier_delim=self.hier_delim) logger.debug("Simple type info key: %r", simple_type_info.keys()) idxmap = defaultdict(dict) for orig_k, v in sorted(doc.items(), key=lambda _k: _k[0]): k = RE_HTTP_ARRAY_INDEX.sub("", orig_k) member = simple_type_info.get(k, None) if member is None: logger.debug("\tdiscarding field %r" % k) continue if member.can_be_empty: if v != ['empty']: # maybe raise a ValidationError instead? # 'empty' is the only valid value at this point after all continue assert issubclass(member.type, ComplexModelBase) if issubclass(member.type, Array): value = [] elif self.get_cls_attrs(member.type).max_occurs > 1: value = [] else: value = [member.type.get_deserialization_instance(ctx)] # do we have to ignore later assignments? they're illegal # but not harmful. else: # extract native values from the list of strings in the flat dict # entries. value = self._to_native_values(cls, member, orig_k, k, v, req_enc, validator) # assign the native value to the relevant class in the nested object # structure. cinst = retval ctype_info = cls.get_flat_type_info(cls) ccls_attr = self.get_cls_attrs(cls) value = self._cast(ccls_attr, value) idx, nidx = 0, 0 pkey = member.path[0] cfreq_key = cls, idx indexes = deque(RE_HTTP_ARRAY_INDEX.findall(orig_k)) for pkey in member.path[:-1]: nidx = 0 ncls, ninst = ctype_info[pkey], getattr(cinst, pkey, None) nattrs = self.get_cls_attrs(ncls) if issubclass(ncls, Array): ncls, = ncls._type_info.values() ncls_attrs = self.get_cls_attrs(ncls) mo = ncls_attrs.max_occurs if mo > 1: if len(indexes) == 0: nidx = 0 else: nidx = int(indexes.popleft()) if ninst is None: ninst = [] cinst._safe_set(pkey, ninst, ncls, nattrs) if self.strict_arrays: if len(ninst) == 0: newval = ncls.get_deserialization_instance(ctx) ninst.append(newval) frequencies[cfreq_key][pkey] += 1 if nidx > len(ninst): raise ValidationError(orig_k, "%%r Invalid array index %d." % idx) if nidx == len(ninst): ninst.append(ncls.get_deserialization_instance(ctx)) frequencies[cfreq_key][pkey] += 1 cinst = ninst[nidx] else: _m = idxmap[id(ninst)] cidx = _m.get(nidx, None) if cidx is None: cidx = _s2cmi(_m, nidx) newval = ncls.get_deserialization_instance(ctx) ninst.insert(cidx, newval) frequencies[cfreq_key][pkey] += 1 cinst = ninst[cidx] assert cinst is not None, ninst else: if ninst is None: ninst = ncls.get_deserialization_instance(ctx) cinst._safe_set(pkey, ninst, ncls, nattrs) frequencies[cfreq_key][pkey] += 1 cinst = ninst cfreq_key = cfreq_key + (ncls, nidx) idx = nidx ctype_info = ncls.get_flat_type_info(ncls) frequencies[cfreq_key][member.path[-1]] += len(value) member_attrs = self.get_cls_attrs(member.type) if member_attrs.max_occurs > 1: _v = getattr(cinst, member.path[-1], None) is_set = True if _v is None: is_set = cinst._safe_set(member.path[-1], value, member.type, member_attrs) else: _v.extend(value) set_skip = 'set ' if is_set else 'SKIP' logger.debug("\t%s arr %r(%r) = %r" % (set_skip, member.path, pkey, value)) else: is_set = cinst._safe_set(member.path[-1], value[0], member.type, member_attrs) set_skip = 'set ' if is_set else 'SKIP' logger.debug("\t%s val %r(%r) = %r" % (set_skip, member.path, pkey, value[0])) if validator is self.SOFT_VALIDATION: logger.debug("\tvalidate_freq: \n%r", frequencies) for k, d in frequencies.items(): for i, path_cls in enumerate(k[:-1:2]): attrs = self.get_cls_attrs(path_cls) if not attrs.validate_freq: logger.debug("\t\tskip validate_freq: %r", k[:i*2]) break else: path_cls = k[-2] logger.debug("\t\tdo validate_freq: %r", k) self._check_freq_dict(path_cls, d) if issubclass(cls, Array): # unwrap the request object array_name, = cls._type_info.keys() retval = getattr(retval, array_name) return retval def object_to_simple_dict(self, cls, inst, retval=None, prefix=None, subinst_eater=lambda prot, v, t: v, tags=None): """Converts a native python object to a flat dict. See :func:`spyne.model.complex.ComplexModelBase.get_flat_type_info`. """ if retval is None: retval = {} if prefix is None: prefix = [] if inst is None and self.get_cls_attrs(cls).min_occurs == 0: return retval if tags is None: tags = set([id(inst)]) else: if id(inst) in tags: return retval if issubclass(cls, ComplexModelBase): fti = cls.get_flat_type_info(cls) for k, v in fti.items(): new_prefix = list(prefix) cls_attrs = self.get_cls_attrs(v) sub_name = cls_attrs.sub_name if sub_name is None: sub_name = k new_prefix.append(sub_name) subinst = getattr(inst, k, None) if (issubclass(v, Array) or v.Attributes.max_occurs > 1) and \ subinst is not None: if issubclass(v, Array): subtype, = v._type_info.values() else: subtype = v # for simple types, the same key is repeated with multiple # values if issubclass(subtype, SimpleModel): key = self.hier_delim.join(new_prefix) l = [] for ssv in subinst: l.append(subinst_eater(self, ssv, subtype)) retval[key] = l else: # for complex types, brackets are used for each value. last_prefix = new_prefix[-1] i = -1 for i, ssv in enumerate(subinst): new_prefix[-1] = '%s[%d]' % (last_prefix, i) self.object_to_simple_dict(subtype, ssv, retval, new_prefix, subinst_eater=subinst_eater, tags=tags) if i == -1: key = self.hier_delim.join(new_prefix) retval[key] = 'empty' else: self.object_to_simple_dict(v, subinst, retval, new_prefix, subinst_eater=subinst_eater, tags=tags) else: key = self.hier_delim.join(prefix) if key in retval: raise ValueError("%r.%s conflicts with previous value %r" % (cls, key, retval[key])) retval[key] = subinst_eater(self, inst, cls) return retval spyne-spyne-2.14.0/spyne/protocol/html/000077500000000000000000000000001417664205300200435ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/html/__init__.py000066400000000000000000000030031417664205300221500ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ This package contains some basic html output protocols. """ from spyne.protocol.html._base import HtmlBase from spyne.protocol.html._base import HtmlCloth from spyne.protocol.html._base import parse_html_fragment_file from spyne.protocol.html.table import HtmlColumnTable from spyne.protocol.html.table import HtmlRowTable from spyne.protocol.html.microformat import HtmlMicroFormat from spyne.protocol.html.addtl import PrettyFormat from spyne.protocol.html.addtl import BooleanListProtocol # FIXME: REMOVE ME def translate(cls, locale, default): retval = None if cls.Attributes.translations is not None: retval = cls.Attributes.translations.get(locale, None) if retval is None: return default return retval spyne-spyne-2.14.0/spyne/protocol/html/_base.py000066400000000000000000000200271417664205300214670ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from collections import defaultdict from lxml import etree, html from lxml.html.builder import E from spyne.util import coroutine, Break, six from spyne.util.oset import oset from spyne.util.etreeconv import dict_to_etree from spyne.protocol.cloth import XmlCloth from spyne.protocol.cloth._base import XmlClothProtocolContext def parse_html_fragment_file(T_FILES): elt = html.fromstring(open(T_FILES).read()) elt.getparent().remove(elt) return elt class HtmlClothProtocolContext(XmlClothProtocolContext): def __init__(self, parent, transport, type=None): super(HtmlClothProtocolContext, self).__init__(parent, transport, type) self.assets = [] self.eltstack = defaultdict(list) self.ctxstack = defaultdict(list) self.rootstack = oset() self.tags = set() self.objcache = dict() # these are supposed to be for neurons.base.screen.ScreenBase subclasses self.screen = None self.prev_view = None self.next_view = None class HtmlCloth(XmlCloth): mime_type = 'text/html; charset=UTF-8' def __init__(self, app=None, encoding='utf8', mime_type=None, ignore_uncap=False, ignore_wrappers=False, cloth=None, cloth_parser=None, polymorphic=True, strip_comments=True, hier_delim='.', doctype=None): super(HtmlCloth, self).__init__(app=app, encoding=encoding, mime_type=mime_type, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, cloth=cloth, cloth_parser=cloth_parser, polymorphic=polymorphic, strip_comments=strip_comments) self.hier_delim = hier_delim self.doctype = doctype self.default_method = 'html' def _parse_file(self, file_name, cloth_parser): if cloth_parser is None: cloth_parser = html.HTMLParser() cloth = html.parse(file_name, parser=cloth_parser) return cloth.getroot() def docfile(self, *args, **kwargs): logger.debug("Starting file with %r %r", args, kwargs) return etree.htmlfile(*args, **kwargs) def get_context(self, parent, transport): return HtmlClothProtocolContext(parent, transport) @staticmethod def get_class_cloth(cls): return cls.Attributes._html_cloth @staticmethod def get_class_root_cloth(cls): return cls.Attributes._html_root_cloth def dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(repr(inst)) @staticmethod def add_html_attr(attr_name, attr_dict, class_name): if attr_name in attr_dict: attr_dict[attr_name] = ' '.join( (attr_dict.get('class', ''), class_name)) else: attr_dict[attr_name] = class_name @staticmethod def add_style(attr_dict, data): style = attr_dict.get('style', None) if style is not None: attr_dict['style'] = ';'.join(style, data) else: attr_dict['style'] = data @staticmethod def selsafe(s): return s.replace('[', '').replace(']', '').replace('.', '__') @coroutine def complex_to_parent(self, ctx, cls, inst, parent, name, use_ns=False, **kwargs): inst = cls.get_serialization_instance(inst) # TODO: Put xml attributes as well in the below element() call. with parent.element(name): ret = self._write_members(ctx, cls, inst, parent, use_ns=False, **kwargs) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass def gen_anchor(self, cls, inst, name, anchor_class=None): assert name is not None cls_attrs = self.get_cls_attrs(cls) href = getattr(inst, 'href', None) if href is None: # this is not a AnyUri.Value instance. href = inst content = None text = cls_attrs.text else: content = getattr(inst, 'content', None) text = getattr(inst, 'text', None) if text is None: text = cls_attrs.text if anchor_class is None: anchor_class = cls_attrs.anchor_class if text is None: text = name retval = E.a(text) if href is not None: retval.attrib['href'] = href if anchor_class is not None: retval.attrib['class'] = anchor_class if content is not None: retval.append(content) return retval def any_uri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): retval = self.gen_anchor(cls, inst, name) parent.write(retval) def imageuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): # with ImageUri, content is ignored. href = getattr(inst, 'href', None) if href is None: # this is not a AnyUri.Value instance. href = inst text = getattr(cls.Attributes, 'text', None) else: text = getattr(inst, 'text', None) if text is None: text = getattr(cls.Attributes, 'text', None) retval = E.img(src=href) if text is not None: retval.attrib['alt'] = text parent.write(retval) def byte_array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): ret = self.to_unicode(cls, inst, self.binary_encoding) if ret is not None: parent.write(ret) def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): ret = self.to_unicode(cls, inst) if ret is not None: parent.write(ret) def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): pass def any_xml_to_parent(self, ctx, cls, inst, parent, name, **kwargs): if isinstance(inst, (six.text_type, six.binary_type)): inst = etree.fromstring(inst) parent.write(inst) def any_html_to_parent(self, ctx, cls, inst, parent, name, **kwargs): cls_attrs = self.get_cls_attrs(cls) if cls_attrs.as_string: if not (isinstance(inst, str) or isinstance(inst, six.text_type)): inst = html.tostring(inst) else: if isinstance(inst, str) or isinstance(inst, six.text_type): inst = html.fromstring(inst) parent.write(inst) def any_to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(inst) def any_dict_to_parent(self, ctx, cls, inst, parent, name, **kwargs): elt = E('foo') dict_to_etree(inst, elt) parent.write(elt[0]) def fault_to_parent(self, ctx, cls, inst, parent, name, **kwargs): self.complex_to_parent(ctx, cls, inst, parent, name, **kwargs) # FIXME: Deprecated HtmlBase = HtmlCloth spyne-spyne-2.14.0/spyne/protocol/html/addtl.py000066400000000000000000000033131417664205300215050ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from lxml.builder import E from pprint import pformat from spyne import Boolean from spyne.protocol.html import HtmlBase class PrettyFormat(HtmlBase): def to_parent(self, ctx, cls, inst, parent, name, **kwargs): parent.write(E.pre(pformat(inst))) class BooleanListProtocol(HtmlBase): def __init__(self, nothing=None): super(BooleanListProtocol, self).__init__() self.nothing = nothing def to_parent(self, ctx, cls, inst, parent, name, nosubprot=False, **kwargs): if inst is None: return wrote_nothing = True for k, v in cls.get_flat_type_info(cls).items(): if not issubclass(v, Boolean): continue if getattr(inst, k, False): parent.write(E.p(self.trc(cls, ctx.locale, k))) wrote_nothing = False if wrote_nothing and self.nothing is not None: parent.write(E.p(self.nothing)) spyne-spyne-2.14.0/spyne/protocol/html/microformat.py000066400000000000000000000176221417664205300227470ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from inspect import isgenerator from lxml.html.builder import E from spyne.util import six, coroutine, Break from spyne.util.cdict import cdict from spyne.model import Array, AnyHtml, ComplexModelBase, ByteArray, \ ModelBase, PushBase, ImageUri, AnyUri from spyne.protocol.html import HtmlBase class HtmlMicroFormat(HtmlBase): def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=False, cloth=None, cloth_parser=None, polymorphic=True, doctype="", root_tag='div', child_tag='div', field_name_attr='class', field_name_tag=None, field_name_class='field_name', before_first_root=None): """Protocol that returns the response object according to the "html microformat" specification. See https://en.wikipedia.org/wiki/Microformats for more info. The simple flavour is like the XmlDocument protocol, but returns data in
or tags. :param app: A spyne.application.Application instance. :param root_tag: The type of the root tag that encapsulates the return data. :param child_tag: The type of the tag that encapsulates the fields of the returned object. :param field_name_attr: The name of the attribute that will contain the field names of the complex object children. """ super(HtmlMicroFormat, self).__init__(app=app, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, cloth=cloth, cloth_parser=cloth_parser, polymorphic=polymorphic, hier_delim=None, doctype=doctype) if six.PY2: text_type = basestring else: text_type = str assert isinstance(root_tag, text_type) assert isinstance(child_tag, text_type) assert isinstance(field_name_attr, text_type) assert field_name_tag is None or isinstance(field_name_tag, text_type) self.root_tag = root_tag self.child_tag = child_tag self.field_name_attr = field_name_attr self.field_name_tag = field_name_tag if field_name_tag is not None: self.field_name_tag = E(field_name_tag) self._field_name_class = field_name_class if before_first_root is not None: self.event_manager.add_listener("before_first_root", before_first_root) self.serialization_handlers = cdict({ Array: self.array_to_parent, AnyUri: self.any_uri_to_parent, AnyHtml: self.any_html_to_parent, ImageUri: self.imageuri_to_parent, ByteArray: self.not_supported, ModelBase: self.model_base_to_parent, ComplexModelBase: self.complex_model_to_parent, }) def anyuri_to_parent(self, ctx, cls, inst, parent, name, **kwargs): retval = self.gen_anchor(cls, inst, parent) retval.attrib[self.field_name_attr] = name parent.write(retval) def model_base_to_parent(self, ctx, cls, inst, parent, name, **kwargs): retval = E(self.child_tag, **{self.field_name_attr: name}) data_str = self.to_unicode(cls, inst) if self.field_name_tag is not None: field_name = cls.Attributes.translations.get( name) field_name_tag = self.field_name_tag(field_name, **{'class':self._field_name_class}) field_name_tag.tail = data_str retval.append(field_name_tag) else: retval.text = data_str parent.write(retval) def start_to_parent(self, ctx, cls, inst, parent, name, **kwargs): """This is what subserialize calls""" # if no doctype was written, write it if not getattr(ctx.outprot_ctx, 'doctype_written', False): if len(ctx.protocol.prot_stack) == 1: if self.doctype is not None: parent.write_doctype(self.doctype) # set this to true as no doctype can be written after this # stage anyway. ctx.outprot_ctx.doctype_written = True return self.to_parent(ctx, cls, inst, parent, name, **kwargs) @coroutine def complex_model_to_parent(self, ctx, cls, inst, parent, name, use_ns=False, **kwargs): attrs = {self.field_name_attr: name} if not getattr(ctx.protocol, 'before_first_root', False): self.event_manager.fire_event("before_first_root", ctx, cls, inst, parent, name, **kwargs) ctx.protocol.before_first_root = True with parent.element(self.root_tag, attrs): ret = self._write_members(ctx, cls, inst, parent, use_ns=False, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): attrs = {self.field_name_attr: name} if issubclass(cls, Array): cls, = cls._type_info.values() name = cls.get_type_name() with parent.element(self.root_tag, attrs): if isinstance(inst, PushBase): while True: sv = (yield) ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass else: for sv in inst: ret = self.to_parent(ctx, cls, sv, parent, name, from_arr=True, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as e: try: ret.throw(e) except StopIteration: pass def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): return [ E(self.child_tag, **{self.field_name_attr: name}) ] # FIXME: yuck. from spyne.protocol.cloth import XmlCloth XmlCloth.HtmlMicroFormat = HtmlMicroFormat spyne-spyne-2.14.0/spyne/protocol/html/table/000077500000000000000000000000001417664205300211325ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/html/table/__init__.py000066400000000000000000000002571417664205300232470ustar00rootroot00000000000000 from spyne.protocol.html.table._base import HtmlTableBase from spyne.protocol.html.table.row import HtmlRowTable from spyne.protocol.html.table.column import HtmlColumnTable spyne-spyne-2.14.0/spyne/protocol/html/table/_base.py000066400000000000000000000055111417664205300225570ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.protocol.html import HtmlBase class HtmlTableBase(HtmlBase): def __init__(self, app=None, ignore_uncap=False, ignore_wrappers=True, cloth=None, cloth_parser=None, header=True, table_name_attr='class', table_name=None, table_class=None, border=0, row_class=None, field_name_attr='class', field_type_name_attr='class', cell_class=None, header_cell_class=None, polymorphic=True, hier_delim='.', doctype=None, link_gen=None, mrpc_delim_text='|', table_width=None): super(HtmlTableBase, self).__init__(app=app, ignore_uncap=ignore_uncap, ignore_wrappers=ignore_wrappers, cloth=cloth, cloth_parser=cloth_parser, polymorphic=polymorphic, hier_delim=hier_delim, doctype=doctype) self.header = header self.table_name_attr = table_name_attr self.table_name = table_name self.field_name_attr = field_name_attr self.field_type_name_attr = field_type_name_attr self.border = border self.row_class = row_class self.cell_class = cell_class self.header_cell_class = header_cell_class self.link_gen = link_gen self.table_class = table_class self.table_width = table_width self.mrpc_delim_text = mrpc_delim_text def null_to_parent(self, ctx, cls, inst, parent, name, **kwargs): pass def add_field_attrs(self, attr_dict, name, cls): if self.field_name_attr: self.add_html_attr(self.field_name_attr, attr_dict, name) if self.field_type_name_attr: types = set() c = cls while c is not None: if c.Attributes._explicit_type_name or c.__extends__ is None: types.add(c.get_type_name()) c = c.__extends__ self.add_html_attr(self.field_type_name_attr, attr_dict, ' '.join(types)) spyne-spyne-2.14.0/spyne/protocol/html/table/column.py000066400000000000000000000322461417664205300230100ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from lxml.html.builder import E from spyne import ModelBase, ComplexModelBase, Array from spyne.util import coroutine, Break, urlencode from spyne.util.oset import oset from spyne.protocol.html.table import HtmlTableBase class HtmlColumnTableRowProtocol(object): def column_table_gen_header(self, ctx, cls, parent, name, **kwargs): return False def column_table_before_row(self, ctx, cls, inst, parent, name, **kwargs): pass def column_table_after_row(self, ctx, cls, inst, parent, name, **kwargs): pass class HtmlColumnTable(HtmlTableBase, HtmlColumnTableRowProtocol): """Protocol that returns the response object as a html table. Returns one record per table row in a table that has as many columns as field names, just like a regular spreadsheet. This is not quite unlike the HtmlMicroFormatprotocol, but returns data as a html table using the tag. Generally used to serialize Array()'s of ComplexModel objects. If an array has prot=HtmlColumnTable, its serializer (what's inside the Array( )) must implement HtmlColumnTableRowProtocol interface. :param app: A spyne.application.Application instance. :param header: Boolean value to determine whether to show field names in the beginning of the table or not. Defaults to True. Set to False to skip headers. :param table_name_attr: The name of the attribute that will contain the response name of the complex object in the table tag. Set to None to disable. :param table_name: When not none, overrides what goes in `table_name_attr`. :param table_class: When not none, specifies what goes in `class` attribute in the `
` tag. Table name gets appended when `table_name_attr == 'class'` :param field_name_attr: The name of the attribute that will contain the field names of the complex object children for every table cell. Set to None to disable. :param row_class: value that goes inside the :param cell_class: value that goes inside the tags are generated before exiting the
:param header_cell_class: value that goes inside the :param mrpc_delim_text: The text that goes between mrpc sessions. """ def __init__(self, *args, **kwargs): before_table = kwargs.pop('before_table', None) super(HtmlColumnTable, self).__init__(*args, **kwargs) self.serialization_handlers.update({ ModelBase: self.model_base_to_parent, ComplexModelBase: self.complex_model_to_parent, Array: self.array_to_parent, }) if before_table is not None: self.event_manager.add_listener("before_table", before_table) def model_base_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): inst_str = '' if inst is not None: inst_str = self.to_unicode(cls, inst) if from_arr: td_attrs = {} self.add_field_attrs(td_attrs, name, cls) parent.write(E.tr(E.td(inst_str, **td_attrs))) else: parent.write(inst_str) @coroutine def _gen_row(self, ctx, cls, inst, parent, name, from_arr=False, array_index=None, **kwargs): # because HtmlForm* protocols don't use the global null handler, it's # possible for null values to reach here. if inst is None: return logger.debug("Generate row for %r", cls) mrpc_delim_elt = '' if self.mrpc_delim_text is not None: mrpc_delim_elt = E.span(self.mrpc_delim_text, **{'class': 'mrpc-delimiter'}) mrpc_delim_elt.tail = ' ' with parent.element('tr'): for k, v in self.sort_fields(cls): cls_attr = self.get_cls_attrs(v) if cls_attr.exc: logger.debug("\tExclude table cell %r type %r for %r", k, v, cls) continue try: sub_value = getattr(inst, k, None) except: # e.g. SQLAlchemy could throw NoSuchColumnError sub_value = None sub_name = cls_attr.sub_name if sub_name is None: sub_name = k if self.hier_delim is not None: if array_index is None: sub_name = "%s%s%s" % (name, self.hier_delim, sub_name) else: sub_name = "%s[%d]%s%s" % (name, array_index, self.hier_delim, sub_name) logger.debug("\tGenerate table cell %r type %r for %r", sub_name, v, cls) td_attrs = {} self.add_field_attrs(td_attrs, cls_attr.sub_name or k, v) if cls_attr.hidden: self.add_style(td_attrs, 'display:None') with parent.element('td', td_attrs): ret = self.to_parent(ctx, v, sub_value, parent, sub_name, from_arr=from_arr, array_index=array_index, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass m = cls.Attributes.methods if m is not None and len(m) > 0: td_attrs = {'class': 'mrpc-cell'} with parent.element('td', td_attrs): first = True for mn, md in self._methods(ctx, cls, inst): if first: first = False elif mrpc_delim_elt is not None: parent.write(" ") parent.write(mrpc_delim_elt) pd = {} for k, v in self.sort_fields(cls): if getattr(v.Attributes, 'primary_key', None): r = self.to_unicode(v, getattr(inst, k, None)) if r is not None: pd[k] = r params = urlencode(pd) mdid2key = ctx.app.interface.method_descriptor_id_to_key href = mdid2key[id(md)].rsplit("}", 1)[-1] text = md.translate(ctx.locale, md.in_message.get_type_name()) parent.write(E.a( text, href="%s?%s" % (href, params), **{'class': 'mrpc-operation'} )) logger.debug("Generate row for %r done.", cls) self.extend_data_row(ctx, cls, inst, parent, name, array_index=array_index, **kwargs) def _gen_thead(self, ctx, cls, parent, name): logger.debug("Generate header for %r", cls) with parent.element('thead'): with parent.element('tr'): if issubclass(cls, ComplexModelBase): fti = self.sort_fields(cls) for k, v in fti: cls_attr = self.get_cls_attrs(v) if cls_attr.exc: continue th_attrs = {} self.add_field_attrs(th_attrs, k, cls) if cls_attr.hidden: self.add_style(th_attrs, 'display:None') header_name = self.trc(v, ctx.locale, k) parent.write(E.th(header_name, **th_attrs)) m = cls.Attributes.methods if m is not None and len(m) > 0: th_attrs = {'class': 'mrpc-cell'} parent.write(E.th(**th_attrs)) else: th_attrs = {} self.add_field_attrs(th_attrs, name, cls) header_name = self.trc(cls, ctx.locale, name) parent.write(E.th(header_name, **th_attrs)) self.extend_header_row(ctx, cls, parent, name) @coroutine def _gen_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs): logger.debug("Generate table for %r", cls) cls_attrs = self.get_cls_attrs(cls) attrib = {} table_class = oset() if self.table_class is not None: table_class.add(self.table_class) if self.table_name_attr is not None: tn = (self.table_name if self.table_name is not None else cls.get_type_name()) if self.table_name_attr == 'class': table_class.add(tn) else: attrib[self.table_name_attr] = tn attrib['class'] = ' '.join(table_class) if self.table_width is not None: attrib['width'] = self.table_width self.event_manager.fire_event('before_table', ctx, cls, inst, parent, name, prot=self, **kwargs) with parent.element('table', attrib): write_header = self.header if cls_attrs.header is False: write_header = cls_attrs.header if write_header: ret = False subprot = self.get_subprot(ctx, cls_attrs) if subprot is not None: ret = subprot.column_table_gen_header(ctx, cls, parent, name) if not ret: self._gen_thead(ctx, cls, parent, name) with parent.element('tbody'): ret = gen_rows(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass self.extend_table(ctx, cls, parent, name, **kwargs) def complex_model_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): # If this is direct child of an array, table is already set up in # array_to_parent. if from_arr: return self._gen_row(ctx, cls, inst, parent, name, **kwargs) else: return self.wrap_table(ctx, cls, inst, parent, name, self._gen_row, **kwargs) def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): return self.wrap_table(ctx, cls, inst, parent, name, super(HtmlColumnTable, self).array_to_parent, **kwargs) def wrap_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs): return self._gen_table(ctx, cls, inst, parent, name, gen_rows, **kwargs) def extend_table(self, ctx, cls, parent, name, **kwargs): """This is called as the last operation during the table body generation after all the
tag which in turn is inside a tag.""" def extend_data_row(self, ctx, cls, inst, parent, name, **kwargs): """This is called as the last operation during the row generation after all the tag which in turn is inside a tag.""" def extend_header_row(self, ctx, cls, parent, name, **kwargs): """This is called once as the last operation during the table header generation after all the tag which in turn is inside a tag.""" spyne-spyne-2.14.0/spyne/protocol/html/table/row.py000066400000000000000000000221701417664205300223150ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) from inspect import isgenerator from lxml.html.builder import E from spyne import ModelBase, ByteArray, ComplexModelBase, Array, AnyUri, \ ImageUri from spyne.util import coroutine, Break from spyne.util.cdict import cdict from spyne.protocol.html.table import HtmlTableBase class HtmlRowTable(HtmlTableBase): """Protocol that returns the response object as a html table. The simple flavour is like the HtmlMicroFormatprotocol, but returns data as a html table using the
tags are generated before exiting the
tags are generated before exiting the
tag. Returns one record per table in a table with two columns. :param app: A spyne.application.Application instance. :param header: Boolean value to determine whether to show field names in the beginning of the table or not. Defaults to True. Set to False to skip headers. :param table_name_attr: The name of the attribute that will contain the response name of the complex object in the table tag. Set to None to disable. :param table_name: When not none, overrides what goes in `table_name_attr`. :param table_class: When not none, specifies what goes in `class` attribute in the `
` tag. Table name gets appended when `table_name_attr == 'class'` :param field_name_attr: The name of the attribute that will contain the field names of the complex object children for every table cell. Set to None to disable. :param row_class: value that goes inside the :param cell_class: value that goes inside the
:param header_cell_class: value that goes inside the """ def __init__(self, *args, **kwargs): super(HtmlRowTable, self).__init__(*args, **kwargs) self.serialization_handlers = cdict({ ModelBase: self.model_base_to_parent, AnyUri: self.any_uri_to_parent, ImageUri: self.imageuri_to_parent, ByteArray: self.not_supported, ComplexModelBase: self.complex_model_to_parent, Array: self.array_to_parent, }) def model_base_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): if from_arr: td_attrib = {} if False and self.field_name_attr: td_attrib[self.field_name_attr] = name parent.write(E.tr(E.td(self.to_unicode(cls, inst), **td_attrib))) else: parent.write(self.to_unicode(cls, inst)) @coroutine def complex_model_to_parent(self, ctx, cls, inst, parent, name, from_arr=False, **kwargs): attrib = {} if self.table_name_attr is not None: attrib[self.table_name_attr] = cls.get_type_name() if self.table_width is not None: attrib['width'] = self.table_width with parent.element('table', attrib): with parent.element('tbody'): for k, v in self.sort_fields(cls): sub_attrs = self.get_cls_attrs(v) if sub_attrs.exc: logger.debug("\tExclude table cell %r type %r for %r", k, v, cls) continue try: sub_value = getattr(inst, k, None) except: # e.g. SQLAlchemy could throw NoSuchColumnError sub_value = None sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k tr_attrs = {} if self.row_class is not None: self.add_html_attr('class', tr_attrs, self.row_class) with parent.element('tr', tr_attrs): th_attrs = {} if self.header_cell_class is not None: self.add_html_attr('class', th_attrs, self.header_cell_class) self.add_field_attrs(th_attrs, sub_name, v) if sub_attrs.hidden: self.add_style(th_attrs, 'display:None') if self.header: parent.write(E.th( self.trc(v, ctx.locale, sub_name), **th_attrs )) td_attrs = {} if self.cell_class is not None: self.add_html_attr('class', td_attrs, self.cell_class) self.add_field_attrs(td_attrs, sub_name, v) if sub_attrs.hidden: self.add_style(td_attrs, 'display:None') with parent.element('td', td_attrs): ret = self.to_parent(ctx, v, sub_value, parent, sub_name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass @coroutine def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs): with parent.element('div'): if issubclass(cls, ComplexModelBase): ret = super(HtmlRowTable, self).array_to_parent( ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass else: table_attrib = {} if self.table_name_attr: table_attrib = {self.table_name_attr: name} if self.table_width is not None: table_attrib['width'] = self.table_width with parent.element('table', table_attrib): tr_attrib = {} if self.row_class is not None: tr_attrib['class'] = self.row_class with parent.element('tr', tr_attrib): if self.header: parent.write(E.th(self.trc(cls, ctx.locale, cls.get_type_name()))) td_attrs = {} if self.cell_class is not None: self.add_html_attr('class', td_attrs, self.cell_class) self.add_field_attrs(td_attrs, name, cls) cls_attrs = self.get_cls_attrs(cls) if cls_attrs.hidden: self.add_style(td_attrs, 'display:None') with parent.element('td', td_attrs): with parent.element('table'): ret = super(HtmlRowTable, self) \ .array_to_parent(ctx, cls, inst, parent, name, **kwargs) if isgenerator(ret): try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass spyne-spyne-2.14.0/spyne/protocol/http.py000066400000000000000000000427611417664205300204420ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.http`` module contains the HttpRpc protocol implementation. This module is EXPERIMENTAL. You may not recognize the code here next time you look at it. """ from __future__ import print_function import logging logger = logging.getLogger(__name__) import re import pytz import tempfile from spyne import BODY_STYLE_WRAPPED, MethodDescriptor, PushBase from spyne.util import six, coroutine, Break from spyne.util.six import string_types, BytesIO from spyne.error import ResourceNotFoundError from spyne.model.binary import BINARY_ENCODING_URLSAFE_BASE64, File from spyne.model.primitive import DateTime from spyne.protocol.dictdoc import SimpleDictDocument TEMPORARY_DIR = None STREAM_READ_BLOCK_SIZE = 0x4000 SWAP_DATA_TO_FILE_THRESHOLD = 512 * 1024 _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") _QuotePatt = re.compile(r"[\\].") _nulljoin = ''.join # this is twisted's _idnaBytes. it's not possible to import twisted at this # stage so here we are def _host_to_bytes(text): try: import idna except ImportError: return text.encode("idna") else: return idna.encode(text) def _unquote_cookie(str): """Handle double quotes and escaping in cookie values. This method is copied verbatim from the Python 3.5 standard library (http.cookies._unquote) so we don't have to depend on non-public interfaces. """ # If there aren't any doublequotes, # then there can't be any special characters. See RFC 2109. if str is None or len(str) < 2: return str if str[0] != '"' or str[-1] != '"': return str # We have to assume that we must decode this string. # Down to work. # Remove the "s str = str[1:-1] # Check for special sequences. Examples: # \012 --> \n # \" --> " # i = 0 n = len(str) res = [] while 0 <= i < n: o_match = _OctalPatt.search(str, i) q_match = _QuotePatt.search(str, i) if not o_match and not q_match: # Neither matched res.append(str[i:]) break # else: j = k = -1 if o_match: j = o_match.start(0) if q_match: k = q_match.start(0) if q_match and (not o_match or k < j): # QuotePatt matched res.append(str[i:k]) res.append(str[k+1]) i = k + 2 else: # OctalPatt matched res.append(str[i:j]) res.append(chr(int(str[j+1:j+4], 8))) i = j + 4 return _nulljoin(res) def _parse_cookie(cookie): """Parse a ``Cookie`` HTTP header into a dict of name/value pairs. This function attempts to mimic browser cookie parsing behavior; it specifically does not follow any of the cookie-related RFCs (because browsers don't either). The algorithm used is identical to that used by Django version 1.9.10. """ retval = {} for chunk in cookie.split(';'): if '=' in chunk: key, val = chunk.split('=', 1) else: # Assume an empty name per # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 key, val = '', chunk key, val = key.strip(), val.strip() if key or val: # unquote using Python's algorithm. retval[key] = _unquote_cookie(val) return retval def get_stream_factory(dir=None, delete=True): def stream_factory(total_content_length, filename, content_type, content_length=None): if total_content_length >= SWAP_DATA_TO_FILE_THRESHOLD or \ delete == False: if delete == False: # You need python >= 2.6 for this. retval = tempfile.NamedTemporaryFile('wb+', dir=dir, delete=delete) else: retval = tempfile.NamedTemporaryFile('wb+', dir=dir) else: retval = BytesIO() return retval return stream_factory _weekday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] _month = ['w00t', "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] def _header_to_bytes(prot, val, cls): if issubclass(cls, DateTime): if val.tzinfo is not None: val = val.astimezone(pytz.utc) else: val = val.replace(tzinfo=pytz.utc) return "%s, %02d %s %04d %02d:%02d:%02d GMT" % ( _weekday[val.weekday()], val.day, _month[val.month], val.year, val.hour, val.minute, val.second) else: # because wsgi_ref wants header values in unicode. return prot.to_unicode(cls, val) class HttpRpc(SimpleDictDocument): """The so-called HttpRpc protocol implementation. It only works with Http (wsgi and twisted) transports. :param app: An :class:'spyne.application.Application` instance. :param validator: Validation method to use. One of (None, 'soft') :param mime_type: Default mime type to set. Default is 'application/octet-stream' :param tmp_dir: Temporary directory to store partial file uploads. Default is to use the OS default. :param tmp_delete_on_close: The ``delete`` argument to the :class:`tempfile.NamedTemporaryFile`. See: http://docs.python.org/2/library/tempfile.html#tempfile.NamedTemporaryFile. :param ignore_uncap: As HttpRpc can't serialize complex models, it throws a server exception when the return type of the user function is Complex. Passing ``True`` to this argument prevents that by ignoring the return value. """ mime_type = 'text/plain' default_binary_encoding = BINARY_ENCODING_URLSAFE_BASE64 default_string_encoding = 'UTF-8' type = set(SimpleDictDocument.type) type.add('http') def __init__(self, app=None, validator=None, mime_type=None, tmp_dir=None, tmp_delete_on_close=True, ignore_uncap=False, parse_cookie=True, hier_delim=".", strict_arrays=False): super(HttpRpc, self).__init__(app, validator, mime_type, ignore_uncap=ignore_uncap, hier_delim=hier_delim, strict_arrays=strict_arrays) self.tmp_dir = tmp_dir self.tmp_delete_on_close = tmp_delete_on_close self.parse_cookie = parse_cookie def get_tmp_delete_on_close(self): return self.__tmp_delete_on_close def set_tmp_delete_on_close(self, val): self.__tmp_delete_on_close = val self.stream_factory = get_stream_factory(self.tmp_dir, self.__tmp_delete_on_close) tmp_delete_on_close = property(get_tmp_delete_on_close, set_tmp_delete_on_close) def set_validator(self, validator): if validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: self.validator = None else: raise ValueError(validator) def create_in_document(self, ctx, in_string_encoding=None): assert ctx.transport.type.endswith('http'), \ ("This protocol only works with an http transport, not %r, (in %r)" % (ctx.transport.type, ctx.transport)) ctx.in_document = ctx.transport.req ctx.transport.request_encoding = in_string_encoding def decompose_incoming_envelope(self, ctx, message_type): assert message_type == SimpleDictDocument.REQUEST ctx.transport.itself.decompose_incoming_envelope( self, ctx, message_type) if self.parse_cookie: cookies = ctx.in_header_doc.get('cookie', None) if cookies is None: cookies = ctx.in_header_doc.get('Cookie', None) if cookies is not None: for cookie_string in cookies: logger.debug("Loading cookie string %r", cookie_string) cookie = _parse_cookie(cookie_string) for k, v in cookie.items(): l = ctx.in_header_doc.get(k, []) l.append(v) ctx.in_header_doc[k] = l logger.debug('\theader : %r' % (ctx.in_header_doc)) logger.debug('\tbody : %r' % (ctx.in_body_doc)) def deserialize(self, ctx, message): assert message in (self.REQUEST,) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) req_enc = getattr(ctx.transport, 'request_encoding', None) if req_enc is None: req_enc = ctx.in_protocol.default_string_encoding if ctx.descriptor.in_header is not None: # HttpRpc supports only one header class in_header_class = ctx.descriptor.in_header[0] ctx.in_header = self.simple_dict_to_object(ctx, ctx.in_header_doc, in_header_class, self.validator, req_enc=req_enc) if ctx.descriptor.in_message is not None: ctx.in_object = self.simple_dict_to_object(ctx, ctx.in_body_doc, ctx.descriptor.in_message, self.validator, req_enc=req_enc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): retval = None assert message in (self.RESPONSE,) if ctx.out_document is not None: return if ctx.out_error is not None: ctx.transport.mime_type = 'text/plain' ctx.out_document = ctx.out_error.to_bytes_iterable(ctx.out_error) else: retval = self._handle_rpc(ctx) self.event_manager.fire_event('serialize', ctx) return retval @coroutine def _handle_rpc_nonempty(self, ctx): result_class = ctx.descriptor.out_message out_class = None out_object = None if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: fti = result_class.get_flat_type_info(result_class) if len(fti) > 1 and not self.ignore_uncap: raise TypeError("HttpRpc protocol can only serialize " "functions with a single return type.") if len(fti) == 1: out_class, = fti.values() out_object, = ctx.out_object else: out_class = result_class out_object, = ctx.out_object if out_class is not None: if issubclass(out_class, File) and not \ isinstance(out_object, (list, tuple, string_types)) \ and out_object.type is not None: ctx.transport.set_mime_type(str(out_object.type)) ret = self.to_bytes_iterable(out_class, out_object) if not isinstance(ret, PushBase): ctx.out_document = ret else: ctx.transport.itself.set_out_document_push(ctx) while True: sv = yield ctx.out_document.send(sv) def _handle_rpc(self, ctx): retval = None # assign raw result to its wrapper, result_message if ctx.out_object is None or len(ctx.out_object) < 1: ctx.out_document = [''] else: retval = self._handle_rpc_nonempty(ctx) header_class = ctx.descriptor.out_header if header_class is not None: # HttpRpc supports only one header class header_class = header_class[0] # header if ctx.out_header is not None: out_header = ctx.out_header if isinstance(ctx.out_header, (list, tuple)): out_header = ctx.out_header[0] ctx.out_header_doc = self.object_to_simple_dict(header_class, out_header, subinst_eater=_header_to_bytes) return retval def create_out_string(self, ctx, out_string_encoding='utf8'): if ctx.out_string is not None: return ctx.out_string = ctx.out_document def boolean_from_bytes(self, cls, string): return string.lower() in ('true', '1', 'checked', 'on') def integer_from_bytes(self, cls, string): if string == '': return None return super(HttpRpc, self).integer_from_bytes(cls, string) _fragment_pattern_re = re.compile('<([A-Za-z0-9_]+)>') _full_pattern_re = re.compile('{([A-Za-z0-9_]+)}') _fragment_pattern_b_re = re.compile(b'<([A-Za-z0-9_]+)>') _full_pattern_b_re = re.compile(b'{([A-Za-z0-9_]+)}') class HttpPattern(object): """Experimental. Stay away. :param address: Address pattern :param verb: HTTP Verb pattern :param host: HTTP "Host:" header pattern """ URL_ENCODING = 'utf8' HOST_ENCODING = 'idna' VERB_ENCODING = 'latin1' # actually ascii but latin1 is what pep 333 needs @classmethod def _compile_url_pattern(cls, pattern_s): """where <> placeholders don't contain slashes.""" if pattern_s is None: return None, None if not six.PY2: assert isinstance(pattern_s, six.text_type) pattern = _fragment_pattern_re.sub(r'(?P<\1>[^/]*)', pattern_s) pattern = _full_pattern_re.sub(r'(?P<\1>[^/]*)', pattern) pattern_b = pattern_s.encode(cls.URL_ENCODING) pattern_b = _fragment_pattern_b_re.sub(b'(?P<\\1>[^/]*)', pattern_b) pattern_b = _full_pattern_b_re.sub(b'(?P<\\1>[^/]*)', pattern_b) return re.compile(pattern), re.compile(pattern_b) @classmethod def _compile_host_pattern(cls, pattern): """where <> placeholders don't contain dots.""" if pattern is None: return None, None pattern = _fragment_pattern_re.sub(r'(?P<\1>[^\.]*)', pattern) pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) pattern_b = pattern.encode(cls.HOST_ENCODING) pattern_b = _fragment_pattern_b_re.sub(b'(?P<\\1>[^\.]*)', pattern_b) pattern_b = _full_pattern_b_re.sub(b'(?P<\\1>.*)', pattern_b) return re.compile(pattern), re.compile(pattern_b) @classmethod def _compile_verb_pattern(cls, pattern): """where <> placeholders are same as {} ones.""" if pattern is None: return None, None pattern = _fragment_pattern_re.sub(r'(?P<\1>.*)', pattern) pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern) pattern_b = pattern.encode(cls.VERB_ENCODING) pattern_b = _fragment_pattern_b_re.sub(b'(?P<\\1>.*)', pattern_b) pattern_b = _full_pattern_b_re.sub(b'(?P<\\1>.*)', pattern_b) return re.compile(pattern), re.compile(pattern_b) def __init__(self, address=None, verb=None, host=None, endpoint=None): host = _host_to_bytes(host) if isinstance(host, str) else host self.address = address self.host = host self.verb = verb self.endpoint = endpoint if self.endpoint is not None: assert isinstance(self.endpoint, MethodDescriptor) def hello(self, descriptor): if self.address is None: self.address = descriptor.name @property def address(self): return self.__address @address.setter def address(self, what): if what is not None and not what.startswith('/'): what = '/{}'.format(what) self.__address = what self.address_re, self.address_b_re = self._compile_url_pattern(what) @property def host(self): return self.__host @host.setter def host(self, what): self.__host = what self.host_re, self.host_b_re = self._compile_host_pattern(what) @property def verb(self): return self.__verb @verb.setter def verb(self, what): self.__verb = what self.verb_re, self.verb_b_re = self._compile_verb_pattern(what) def as_werkzeug_rule(self): from werkzeug.routing import Rule from spyne.util.invregexp import invregexp methods = None if self.verb is not None: methods = invregexp(self.verb) host = self.host if host is None: host = '<__ignored>' # for some reason, this is necessary when # host_matching is enabled. return Rule(self.address, host=host, endpoint=self.endpoint.name, methods=methods) def __repr__(self): return "HttpPattern(address=%r, host=%r, verb=%r, endpoint=%r)" % ( self.address, self.host, self.verb, None if self.endpoint is None else self.endpoint.name) spyne-spyne-2.14.0/spyne/protocol/json.py000066400000000000000000000354051417664205300204310ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.json`` package contains the Json-related protocols. Currently, only :class:`spyne.protocol.json.JsonDocument` is supported. Initially released in 2.8.0-rc. Missing Types ============= The JSON standard does not define every type that Spyne supports. These include Date/Time types as well as arbitrary-length integers and arbitrary-precision decimals. Integers are parsed to ``int``\s or ``long``\s seamlessly but ``Decimal``\s are only parsed correctly when they come off as strings. While it's possible to e.g. (de)serialize floats to ``Decimal``\s by adding hooks to ``parse_float`` [#]_ (and convert later as necessary), such customizations apply to the whole incoming document which pretty much messes up ``AnyDict`` serialization and deserialization. It also wasn't possible to work with ``object_pairs_hook`` as Spyne's parsing is always "from outside to inside" whereas ``object_pairs_hook`` is passed ``dict``\s basically in any order "from inside to outside". .. [#] http://docs.python.org/2/library/json.html#json.loads """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from itertools import chain from spyne.util import six try: import simplejson as json from simplejson.decoder import JSONDecodeError except ImportError: import json JSONDecodeError = ValueError from spyne.error import ValidationError from spyne.error import ResourceNotFoundError from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.primitive import Date from spyne.model.primitive import Time from spyne.model.primitive import DateTime from spyne.model.primitive import Double from spyne.model.primitive import Integer from spyne.model.primitive import Boolean from spyne.model.fault import Fault from spyne.protocol.dictdoc import HierDictDocument # TODO: use this as default class JsonEncoder(json.JSONEncoder): def default(self, o): try: return super(JsonEncoder, self).default(o) except TypeError as e: # if json can't serialize it, it's possibly a generator. If not, # additional hacks are welcome :) if logger.level == logging.DEBUG: logger.exception(e) return list(o) NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) class JsonDocument(HierDictDocument): """An implementation of the json protocol that uses simplejson package when available, json package otherwise. :param ignore_wrappers: Does not serialize wrapper objects. :param complex_as: One of (list, dict). When list, the complex objects are serialized to a list of values instead of a dict of key/value pairs. """ mime_type = 'application/json' text_based = True type = set(HierDictDocument.type) type.add('json') default_binary_encoding = BINARY_ENCODING_BASE64 # flags used just for tests _decimal_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False, default_string_encoding=None, polymorphic=False, **kwargs): super(JsonDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic) # this is needed when we're overriding a regular instance attribute # with a property. self.__message = HierDictDocument.__getattribute__(self, 'message') self._from_unicode_handlers[Double] = self._ret_number self._from_unicode_handlers[Boolean] = self._ret_bool self._from_unicode_handlers[Integer] = self._ret_number self._to_unicode_handlers[Double] = self._ret self._to_unicode_handlers[Boolean] = self._ret self._to_unicode_handlers[Integer] = self._ret self.default_string_encoding = default_string_encoding self.kwargs = kwargs def _ret(self, cls, value): return value def _ret_number(self, cls, value): if isinstance(value, NON_NUMBER_TYPES): raise ValidationError(value) if value in (True, False): return int(value) return value def _ret_bool(self, cls, value): if value is None or value in (True, False): return value raise ValidationError(value) def validate(self, key, cls, val): super(JsonDocument, self).validate(key, cls, val) if issubclass(cls, (DateTime, Date, Time)) and not ( isinstance(val, six.string_types) and cls.validate_string(cls, val)): raise ValidationError(key, val) @property def message(self): return self.__message @message.setter def message(self, val): if val is self.RESPONSE and not ('cls' in self.kwargs): self.kwargs['cls'] = JsonEncoder self.__message = val def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document`` using ``ctx.in_string``.""" try: in_string = b''.join(ctx.in_string) if not isinstance(in_string, six.text_type): if in_string_encoding is None: in_string_encoding = self.default_string_encoding if in_string_encoding is not None: in_string = in_string.decode(in_string_encoding) ctx.in_document = json.loads(in_string, **self.kwargs) except JSONDecodeError as e: raise Fault('Client.JsonDecodeError', repr(e)) def create_out_string(self, ctx, out_string_encoding='utf8'): """Sets ``ctx.out_string`` using ``ctx.out_document``.""" if out_string_encoding is None: ctx.out_string = (json.dumps(o, **self.kwargs) for o in ctx.out_document) else: ctx.out_string = ( json.dumps(o, **self.kwargs).encode(out_string_encoding) for o in ctx.out_document) # Continuation of http://stackoverflow.com/a/24184379/1520211 class HybridHttpJsonDocument(JsonDocument): """This protocol lets you have the method name as the last fragment in the request url. Eg. instead of sending a HTTP POST request to http://api.endpoint/json/ containing: :: { "method_name": { "arg1" : 42, "arg2" : "foo" } } you will have to send the request to http://api.endpoint/json/method_name containing: :: { "arg1" : 42, "arg2" : "foo" } Part of request data comes from HTTP and part of it comes from Json, hence the name. """ def create_in_document(self, ctx, in_string_encoding=None): super(HybridHttpJsonDocument, self).create_in_document(ctx) url_fragment = ctx.transport.get_path().split('/')[-1] ctx.in_document = {url_fragment: ctx.in_document} class JsonP(JsonDocument): """The JsonP protocol puts the reponse document inside a designated javascript function call. The input protocol is identical to the JsonDocument protocol. :param callback_name: The name of the function call that will wrapp all response documents. For other arguents, see :class:`spyne.protocol.json.JsonDocument`. """ type = set(HierDictDocument.type) type.add('jsonp') def __init__(self, callback_name, *args, **kwargs): super(JsonP, self).__init__(*args, **kwargs) self.callback_name = callback_name def create_out_string(self, ctx, out_string_encoding='utf8'): super(JsonP, self).create_out_string(ctx, out_string_encoding=out_string_encoding) if out_string_encoding is None: ctx.out_string = chain( (self.callback_name, '('), ctx.out_string, (');',), ) else: ctx.out_string = chain( [self.callback_name.encode(out_string_encoding), b'('], ctx.out_string, [b');'], ) class _SpyneJsonRpc1(JsonDocument): version = 1 VERSION = 'ver' BODY = 'body' HEAD = 'head' FAULT = 'fault' def decompose_incoming_envelope(self, ctx, message=JsonDocument.REQUEST): indoc = ctx.in_document if not isinstance(indoc, dict): raise ValidationError(indoc, "Invalid Request") ver = indoc.get(self.VERSION) if ver is None: raise ValidationError(ver, "Unknown Version") body = indoc.get(self.BODY) err = indoc.get(self.FAULT) if body is None and err is None: raise ValidationError((body, err), "Request data not found") ctx.protocol.error = False if err is not None: ctx.in_body_doc = err ctx.protocol.error = True else: if not isinstance(body, dict): raise ValidationError(body, "Request body not found") if not len(body) == 1: raise ValidationError(body, "Need len(body) == 1") ctx.in_header_doc = indoc.get(self.HEAD) if not isinstance(ctx.in_header_doc, list): ctx.in_header_doc = [ctx.in_header_doc] (ctx.method_request_string,ctx.in_body_doc), = body.items() def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise ResourceNotFoundError(ctx.method_request_string) if ctx.protocol.error: ctx.in_object = None ctx.in_error = self._doc_to_object(ctx, Fault, ctx.in_body_doc) else: if message is self.REQUEST: header_class = ctx.descriptor.in_header body_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_class = ctx.descriptor.out_header body_class = ctx.descriptor.out_message # decode header objects if (ctx.in_header_doc is not None and header_class is not None): headers = [None] * len(header_class) for i, (header_doc, head_class) in enumerate( zip(ctx.in_header_doc, header_class)): if header_doc is not None and i < len(header_doc): headers[i] = self._doc_to_object(ctx, head_class, header_doc) if len(headers) == 1: ctx.in_header = headers[0] else: ctx.in_header = headers # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self._doc_to_object(ctx, body_class, ctx.in_body_doc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) ctx.out_document = { "ver": self.version, } if ctx.out_error is not None: ctx.out_document[self.FAULT] = Fault.to_dict(Fault, ctx.out_error, self) else: if message is self.REQUEST: header_message_class = ctx.descriptor.in_header body_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_message_class = ctx.descriptor.out_header body_message_class = ctx.descriptor.out_message # assign raw result to its wrapper, result_message out_type_info = body_message_class._type_info out_object = body_message_class() bm_attrs = self.get_cls_attrs(body_message_class) keys = iter(out_type_info) values = iter(ctx.out_object) while True: try: k = next(keys) except StopIteration: break try: v = next(values) except StopIteration: v = None out_object._safe_set(k, v, body_message_class, bm_attrs) ctx.out_document[self.BODY] = ctx.out_body_doc = \ self._object_to_doc(body_message_class, out_object) # header if ctx.out_header is not None and header_message_class is not None: if isinstance(ctx.out_header, (list, tuple)): out_headers = ctx.out_header else: out_headers = (ctx.out_header,) ctx.out_header_doc = out_header_doc = [] for header_class, out_header in zip(header_message_class, out_headers): out_header_doc.append(self._object_to_doc(header_class, out_header)) if len(out_header_doc) > 1: ctx.out_document[self.HEAD] = out_header_doc else: ctx.out_document[self.HEAD] = out_header_doc[0] self.event_manager.fire_event('after_serialize', ctx) _json_rpc_flavors = { 'spyne': _SpyneJsonRpc1 } def JsonRpc(flavour, *args, **kwargs): assert flavour in _json_rpc_flavors, "Unknown JsonRpc flavor. " \ "Accepted ones are: %r" % tuple(_json_rpc_flavors) return _json_rpc_flavors[flavour](*args, **kwargs) spyne-spyne-2.14.0/spyne/protocol/msgpack.py000066400000000000000000000312041417664205300210760ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.msgpack`` module contains implementations for protocols that use MessagePack as serializer. Initially released in 2.8.0-rc. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import msgpack from spyne import ValidationError from spyne.util import six from spyne.model.fault import Fault from spyne.model.primitive import Double from spyne.model.primitive import Boolean from spyne.model.primitive import Integer from spyne.protocol.dictdoc import HierDictDocument class MessagePackDecodeError(Fault): CODE = "Client.MessagePackDecodeError" def __init__(self, data=None): super(MessagePackDecodeError, self) \ .__init__(self.CODE, data) NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) class MessagePackDocument(HierDictDocument): """An integration class for the msgpack protocol.""" mime_type = 'application/x-msgpack' text_based = False type = set(HierDictDocument.type) type.add('msgpack') default_string_encoding = 'UTF-8' from_serstr = HierDictDocument.from_bytes to_serstr = HierDictDocument.to_bytes # flags to be used in tests _decimal_as_string = True _huge_numbers_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False, polymorphic=False, key_encoding='utf8', # MessagePackDocument specific mw_packer=msgpack.Packer, mw_unpacker=msgpack.Unpacker, use_list=False, raw=False, use_bin_type=True, **kwargs): super(MessagePackDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic, key_encoding) self.mw_packer = mw_packer self.mw_unpacker = mw_unpacker # unpacker if not raw: self.from_serstr = self.from_unicode if use_bin_type: self.from_serstr = self.from_unicode self.kwargs_packer = dict(kwargs) self.kwargs_unpacker = dict(kwargs) self.kwargs_packer['raw'] = self.kwargs_unpacker['raw'] = raw self.kwargs_packer['use_list'] = self.kwargs_unpacker['use_list'] \ = use_list self.kwargs_packer['use_bin_type'] = use_bin_type self._from_bytes_handlers[Double] = self._ret_number self._from_bytes_handlers[Boolean] = self._ret_bool self._from_bytes_handlers[Integer] = self.integer_from_bytes self._from_unicode_handlers[Double] = self._ret_number self._from_unicode_handlers[Boolean] = self._ret_bool self._from_unicode_handlers[Integer] = self.integer_from_bytes self._to_bytes_handlers[Double] = self._ret_number self._to_bytes_handlers[Boolean] = self._ret_bool self._to_bytes_handlers[Integer] = self.integer_to_bytes self._to_unicode_handlers[Double] = self._ret_number self._to_unicode_handlers[Boolean] = self._ret_bool self._to_unicode_handlers[Integer] = self.integer_to_bytes def _ret(self, _, value): return value def _ret_number(self, _, value): if isinstance(value, NON_NUMBER_TYPES): raise ValidationError(value) if value in (True, False): return int(value) return value def _ret_bool(self, _, value): if value is None or value in (True, False): return value raise ValidationError(value) def get_class_name(self, cls): class_name = cls.get_type_name() if not six.PY2: if not isinstance(class_name, bytes): class_name = class_name.encode(self.default_string_encoding) return class_name def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document``, using ``ctx.in_string``. :param ctx: The MethodContext object :param in_string_encoding: MessagePack is a binary protocol. So this argument is ignored. """ # handle mmap objects from in ctx.in_string as returned by # TwistedWebResource.handle_rpc. if isinstance(ctx.in_string, (list, tuple)) \ and len(ctx.in_string) == 1 \ and isinstance(ctx.in_string[0], memoryview): unpacker = self.mw_unpacker(**self.kwargs_unpacker) unpacker.feed(ctx.in_string[0]) ctx.in_document = next(x for x in unpacker) else: try: ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string)) except ValueError as e: raise MessagePackDecodeError(' '.join(e.args)) def gen_method_request_string(self, ctx): """Uses information in context object to return a method_request_string. Returns a string in the form of "{namespaces}method name". """ mrs, = ctx.in_body_doc.keys() if not six.PY2 and isinstance(mrs, bytes): mrs = mrs.decode(self.key_encoding) return '{%s}%s' % (self.app.interface.get_tns(), mrs) def create_out_string(self, ctx, out_string_encoding='utf8'): ctx.out_string = (msgpack.packb(o) for o in ctx.out_document) def integer_from_bytes(self, cls, value): if isinstance(value, (six.text_type, six.binary_type)): return super(MessagePackDocument, self) \ .integer_from_bytes(cls, value) return value def integer_to_bytes(self, cls, value, **_): # if it's inside the range msgpack can deal with if -1<<63 <= value < 1<<64: return value else: return super(MessagePackDocument, self).integer_to_bytes(cls, value) class MessagePackRpc(MessagePackDocument): """An integration class for the msgpack-rpc protocol.""" mime_type = 'application/x-msgpack' MSGPACK_REQUEST = 0 MSGPACK_RESPONSE = 1 MSGPACK_NOTIFY = 2 MSGPACK_ERROR = 3 def create_out_string(self, ctx, out_string_encoding='utf8'): ctx.out_string = (msgpack.packb(o) for o in ctx.out_document) def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document``, using ``ctx.in_string``. :param ctx: The MethodContext object :param in_string_encoding: MessagePack is a binary protocol. So this argument is ignored. """ # TODO: Use feed api try: ctx.in_document = msgpack.unpackb(b''.join(ctx.in_string), **self.kwargs_unpacker) except ValueError as e: raise MessagePackDecodeError(''.join(e.args)) try: len(ctx.in_document) except TypeError: raise MessagePackDecodeError("Input must be a sequence.") if not (3 <= len(ctx.in_document) <= 4): raise MessagePackDecodeError("Length of input iterable must be " "either 3 or 4") def decompose_incoming_envelope(self, ctx, message): # FIXME: For example: {0: 0, 1: 0, 2: "some_call", 3: [1,2,3]} will also # work. Is this a problem? # FIXME: Msgid is ignored. Is this a problem? msgparams = [] if len(ctx.in_document) == 3: msgtype, msgid, msgname_or_error = ctx.in_document else: msgtype, msgid, msgname_or_error, msgparams = ctx.in_document if not six.PY2: if isinstance(msgname_or_error, bytes): msgname_or_error = msgname_or_error.decode( self.default_string_encoding) if msgtype == MessagePackRpc.MSGPACK_REQUEST: assert message == MessagePackRpc.REQUEST elif msgtype == MessagePackRpc.MSGPACK_RESPONSE: assert message == MessagePackRpc.RESPONSE elif msgtype == MessagePackRpc.MSGPACK_NOTIFY: raise NotImplementedError() else: raise MessagePackDecodeError("Unknown message type %r" % msgtype) ctx.method_request_string = '{%s}%s' % (self.app.interface.get_tns(), msgname_or_error) # MessagePackRpc does not seem to have Header support ctx.in_header_doc = None if isinstance(msgname_or_error, dict) and msgname_or_error: # we got an error ctx.in_error = msgname_or_error else: ctx.in_body_doc = msgparams # logger.debug('\theader : %r', ctx.in_header_doc) # logger.debug('\tbody : %r', ctx.in_body_doc) def deserialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: raise Fault("Client", "Method %r not found." % ctx.method_request_string) # instantiate the result message if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message else: raise Exception("what?") if ctx.in_error: ctx.in_error = Fault(**ctx.in_error) elif body_class: ctx.in_object = self._doc_to_object(ctx, body_class, ctx.in_body_doc, self.validator) else: ctx.in_object = [] self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: ctx.out_document = [ [MessagePackRpc.MSGPACK_ERROR, 0, Fault.to_dict(ctx.out_error.__class__, ctx.out_error, self)] ] return # get the result message if message is self.REQUEST: out_type = ctx.descriptor.in_message msgtype = MessagePackRpc.MSGPACK_REQUEST method_name_or_error = ctx.descriptor.operation_name elif message is self.RESPONSE: out_type = ctx.descriptor.out_message msgtype = MessagePackRpc.MSGPACK_RESPONSE method_name_or_error = None else: raise Exception("what?") if out_type is None: return out_type_info = out_type._type_info # instantiate the result message out_instance = out_type() # assign raw result to its wrapper, result_message for i, (k, v) in enumerate(out_type_info.items()): attrs = self.get_cls_attrs(v) out_instance._safe_set(k, ctx.out_object[i], v, attrs) # transform the results into a dict: if out_type.Attributes.max_occurs > 1: params = (self._to_dict_value(out_type, inst, set()) for inst in out_instance) else: params = self._to_dict_value(out_type, out_instance, set()) ctx.out_document = [[msgtype, 0, method_name_or_error, params]] self.event_manager.fire_event('after_serialize', ctx) spyne-spyne-2.14.0/spyne/protocol/soap/000077500000000000000000000000001417664205300200415ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/protocol/soap/__init__.py000066400000000000000000000022331417664205300221520ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.soap`` package contains an implementation of a subset of the Soap 1.1 standard and awaits for volunteers for implementing the brand new Soap 1.2 standard. Patches are welcome. """ from spyne.protocol.soap.soap11 import Soap11 from spyne.protocol.soap.soap11 import _from_soap from spyne.protocol.soap.soap11 import _parse_xml_string from spyne.protocol.soap.soap12 import Soap12spyne-spyne-2.14.0/spyne/protocol/soap/mime.py000066400000000000000000000235231417664205300213470ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.soap.mime`` module contains additional logic for using optimized encodings for binary when encapsulating Soap 1.1 messages in Http. The functionality in this code seems to work at first glance but is not well tested. Testcases and preferably improvements are most welcome. """ from __future__ import print_function, unicode_literals import logging logger = logging.getLogger(__name__) import re from base64 import b64encode from itertools import chain from lxml import etree from email import generator from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.encoders import encode_7or8bit from spyne import ValidationError from spyne.util import six from spyne.model.binary import ByteArray, File from spyne.const.xml import NS_XOP if six.PY2: from email import message_from_string as message_from_bytes else: from email import message_from_bytes XPATH_NSDICT = dict(xop=NS_XOP) def _join_attachment(ns_soap_env, href_id, envelope, payload, prefix=True): """Places the data from an attachment back into a SOAP message, replacing its xop:Include element or href. Returns a tuple of length 2 with the new message and the number of replacements made :param id: content-id or content-location of attachment :param prefix: Set this to true if id is content-id or false if it is content-location. It prefixes a "cid:" to the href value. :param envelope: soap envelope string to be operated on :param payload: attachment data """ # grab the XML element of the message in the SOAP body soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % ns_soap_env) if soapbody is None: raise ValidationError(None, "SOAP Body tag not found") message = None for child in list(soapbody): if child.tag != "{%s}Fault" % ns_soap_env: message = child break idprefix = '' if prefix: idprefix = "cid:" href_id = "%s%s" % (idprefix, href_id,) num = 0 xpath = ".//xop:Include[@href=\"{}\"]".format(href_id) for num, node in enumerate(message.xpath(xpath, namespaces=XPATH_NSDICT)): parent = node.getparent() parent.remove(node) parent.text = payload return etree.tostring(soaptree), num def collapse_swa(ctx, content_type, ns_soap_env): """ Translates an SwA multipart/related message into an application/soap+xml message. Returns the 'appication/soap+xml' version of the given HTTP body. References: SwA http://www.w3.org/TR/SOAP-attachments XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param content_type: value of the Content-Type header field, parsed by cgi.parse_header() function :param ctx: request context """ envelope = ctx.in_string # convert multipart messages back to pure SOAP mime_type, content_data = content_type if not six.PY2: assert isinstance(mime_type, six.text_type) if u'multipart/related' not in mime_type: return envelope charset = content_data.get('charset', None) if charset is None: charset = 'ascii' boundary = content_data.get('boundary', None) if boundary is None: raise ValidationError(None, u"Missing 'boundary' value from " u"Content-Type header") envelope = list(envelope) # What an ugly hack... request = MIMEMultipart('related', boundary=boundary) msg_string = re.sub(r"\n\n.*", '', request.as_string()) msg_string = chain( (msg_string.encode(charset), generator.NL.encode('ascii')), (e for e in envelope), ) msg_string = b''.join(msg_string) msg = message_from_bytes(msg_string) # our message soapmsg = None root = msg.get_param('start') # walk through sections, reconstructing pure SOAP for part in msg.walk(): # skip the multipart container section if part.get_content_maintype() == 'multipart': continue # detect main soap section if (part.get('Content-ID') and part.get('Content-ID') == root) or \ (root is None and part == msg.get_payload()[0]): soapmsg = part.get_payload() continue # binary packages cte = part.get("Content-Transfer-Encoding") if cte != 'base64': payload = b64encode(part.get_payload(decode=True)) else: payload = part.get_payload() cid = part.get("Content-ID").strip("<>") cloc = part.get("Content-Location") numreplaces = None # Check for Content-ID and make replacement if cid: soapmsg, numreplaces = _join_attachment( ns_soap_env, cid, soapmsg, payload) # Check for Content-Location and make replacement if cloc and not cid and not numreplaces: soapmsg, numreplaces = _join_attachment( ns_soap_env, cloc, soapmsg, payload, False) if soapmsg is None: raise ValidationError(None, "Invalid MtoM request") return (soapmsg,) def apply_mtom(headers, envelope, params, paramvals): """Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. Returns a tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param headers Headers dictionary of the SOAP message that would originally be sent. :param envelope Iterable containing SOAP envelope string that would have originally been sent. :param params params attribute from the Message object used for the SOAP :param paramvals values of the params, passed to Message.to_parent """ # grab the XML element of the message in the SOAP body envelope = ''.join(envelope) soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % _ns_soap_env) message = None for child in list(soapbody): if child.tag == ("{%s}Fault" % _ns_soap_env): return headers, envelope else: message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts. mtompkg = MIMEMultipart('related', boundary='?//<><>spyne_MIME_boundary<>') rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) # Set up multipart headers. del mtompkg['mime-version'] mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del rootpkg['mime-version'] rootpkg.add_header('Content-ID', '') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if issubclass(typ, (ByteArray, File)): id = "SpyneAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = etree.SubElement(param, "{%s}Include" % _ns_xop) incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() if issubclass(type, File): data = paramvals[i].data else: data = ''.join(paramvals[i]) attachment = MIMEApplication(data, _encoder=encode_7or8bit) del attachment['mime-version'] attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. rootpkg.set_payload(etree.tostring(soaptree)) # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return headers, envelope return mtomheaders, [mtombody] spyne-spyne-2.14.0/spyne/protocol/soap/soap11.py000066400000000000000000000351071417664205300215250ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.soap.soap11`` module contains the implementation of a subset of the Soap 1.1 standard. Except the binary optimizations (MtoM, attachments, etc) that are beta quality, this protocol is production quality. One must specifically enable the debug output for the Xml protocol to see the actual document exchange. That's because the xml formatting code is run only when explicitly enabled due to performance reasons. :: logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) Initially released in soaplib-0.8.0. Logs valid documents to %r and invalid documents to %r. """ % (__name__, __name__ + ".invalid") import logging logger = logging.getLogger(__name__) logger_invalid = logging.getLogger(__name__ + ".invalid") import cgi from itertools import chain import spyne.const.xml as ns from lxml import etree from lxml.etree import XMLSyntaxError from lxml.etree import XMLParser from spyne import BODY_STYLE_WRAPPED from spyne.util import six from spyne.const.xml import DEFAULT_NS from spyne.const.http import HTTP_405, HTTP_500 from spyne.error import RequestNotAllowed from spyne.model.fault import Fault from spyne.model.primitive import Date, Time, DateTime from spyne.protocol.xml import XmlDocument from spyne.protocol.soap.mime import collapse_swa from spyne.server.http import HttpTransportContext def _from_soap(in_envelope_xml, xmlids=None, **kwargs): """Parses the xml string into the header and payload. """ ns_soap = kwargs.pop('ns', ns.NS_SOAP11_ENV) if xmlids: resolve_hrefs(in_envelope_xml, xmlids) if in_envelope_xml.tag != '{%s}Envelope' % ns_soap: raise Fault('Client.SoapError', 'No {%s}Envelope element was found!' % ns_soap) header_envelope = in_envelope_xml.xpath('e:Header', namespaces={'e': ns_soap}) body_envelope = in_envelope_xml.xpath('e:Body', namespaces={'e': ns_soap}) if len(header_envelope) == 0 and len(body_envelope) == 0: raise Fault('Client.SoapError', 'Soap envelope is empty!') header = None if len(header_envelope) > 0: header = header_envelope[0].getchildren() body = None if len(body_envelope) > 0 and len(body_envelope[0]) > 0: body = body_envelope[0][0] return header, body def _parse_xml_string(xml_string, parser, charset=None): xml_string = iter(xml_string) chunk = next(xml_string) if isinstance(chunk, six.binary_type): string = b''.join(chain( (chunk,), xml_string )) else: string = ''.join(chain( (chunk,), xml_string )) if charset: string = string.decode(charset) try: try: root, xmlids = etree.XMLID(string, parser) except ValueError as e: logger.debug('ValueError: Deserializing from unicode strings with ' 'encoding declaration is not supported by lxml.') root, xmlids = etree.XMLID(string.encode(charset), parser) except XMLSyntaxError as e: logger_invalid.error("%r in string %r", e, string) raise Fault('Client.XMLSyntaxError', str(e)) return root, xmlids # see http://www.w3.org/TR/2000/NOTE-SOAP-20000508/ # section 5.2.1 for an example of how the id and href attributes are used. def resolve_hrefs(element, xmlids): for e in element: if e.get('id'): continue # don't need to resolve this element elif e.get('href'): resolved_element = xmlids[e.get('href').replace('#', '')] if resolved_element is None: continue resolve_hrefs(resolved_element, xmlids) # copies the attributes [e.set(k, v) for k, v in resolved_element.items()] # copies the children [e.append(child) for child in resolved_element.getchildren()] # copies the text e.text = resolved_element.text else: resolve_hrefs(e, xmlids) return element class Soap11(XmlDocument): """The base implementation of a subset of the Soap 1.1 standard. The document is available here: http://www.w3.org/TR/soap11/ :param app: The owner application instance. :param validator: One of (None, 'soft', 'lxml', 'schema', ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). Both ``'lxml'`` and ``'schema'`` values are equivalent to ``XmlDocument.SCHEMA_VALIDATION``. :param xml_declaration: Whether to add xml_declaration to the responses Default is 'True'. :param cleanup_namespaces: Whether to add clean up namespace declarations in the response document. Default is 'True'. :param encoding: The suggested string encoding for the returned xml documents. The transport can override this. :param pretty_print: When ``True``, returns the document in a pretty-printed format. """ mime_type = 'text/xml; charset=utf-8' type = set(XmlDocument.type) type.update(('soap', 'soap11')) ns_soap_env = ns.NS_SOAP11_ENV ns_soap_enc = ns.NS_SOAP11_ENC def __init__(self, *args, **kwargs): super(Soap11, self).__init__(*args, **kwargs) # SOAP requires DateTime strings to be in iso format. The following # lines make sure custom datetime formatting via # DateTime(dt_format="...") (or similar) is bypassed. self._to_unicode_handlers[Time] = lambda cls, value: value.isoformat() self._to_unicode_handlers[DateTime] = lambda cls, value: value.isoformat() self._from_unicode_handlers[Date] = self.date_from_unicode_iso self._from_unicode_handlers[DateTime] = self.datetime_from_unicode_iso def create_in_document(self, ctx, charset=None): if isinstance(ctx.transport, HttpTransportContext): # according to the soap-via-http standard, soap requests must only # work with proper POST requests. content_type = ctx.transport.get_request_content_type() http_verb = ctx.transport.get_request_method() if content_type is None or http_verb != "POST": ctx.transport.resp_code = HTTP_405 raise RequestNotAllowed( "You must issue a POST request with the Content-Type " "header properly set.") content_type = cgi.parse_header(content_type) ctx.in_string = collapse_swa(ctx, content_type, self.ns_soap_env) ctx.in_document = _parse_xml_string(ctx.in_string, XMLParser(**self.parser_kwargs), charset) def decompose_incoming_envelope(self, ctx, message=XmlDocument.REQUEST): envelope_xml, xmlids = ctx.in_document header_document, body_document = _from_soap(envelope_xml, xmlids, ns=self.ns_soap_env) ctx.in_document = envelope_xml if body_document.tag == '{%s}Fault' % self.ns_soap_env: ctx.in_body_doc = body_document else: ctx.in_header_doc = header_document ctx.in_body_doc = body_document ctx.method_request_string = ctx.in_body_doc.tag self.validate_body(ctx, message) def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE soap message. Returns the corresponding native python object Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.in_body_doc.tag == "{%s}Fault" % self.ns_soap_env: ctx.in_object = None ctx.in_error = self.from_element(ctx, Fault, ctx.in_body_doc) else: if message is self.REQUEST: header_class = ctx.descriptor.in_header body_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_class = ctx.descriptor.out_header body_class = ctx.descriptor.out_message # decode header objects # header elements are returned in header_class order which need not match the incoming XML if (ctx.in_header_doc is not None and header_class is not None): headers = [None] * len(header_class) in_header_dict = dict( [(element.tag, element) for element in ctx.in_header_doc]) for i, head_class in enumerate(header_class): if i < len(header_class): nsval = "{%s}%s" % (head_class.__namespace__, head_class.__type_name__) header_doc = in_header_dict.get(nsval, None) if header_doc is not None: headers[i] = self.from_element(ctx, head_class, header_doc) if len(headers) == 1: ctx.in_header = headers[0] else: ctx.in_header = headers # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self.from_element(ctx, body_class, ctx.in_body_doc) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): """Uses ctx.out_object, ctx.out_header or ctx.out_error to set ctx.out_body_doc, ctx.out_header_doc and ctx.out_document as an lxml.etree._Element instance. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) # construct the soap response, and serialize it nsmap = self.app.interface.nsmap ctx.out_document = etree.Element('{%s}Envelope' % self.ns_soap_env, nsmap=nsmap) if ctx.out_error is not None: # FIXME: There's no way to alter soap response headers for the user. ctx.out_body_doc = out_body_doc = etree.SubElement(ctx.out_document, '{%s}Body' % self.ns_soap_env, nsmap=nsmap) self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, out_body_doc, self.app.interface.get_tns()) else: if message is self.REQUEST: header_message_class = ctx.descriptor.in_header body_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: header_message_class = ctx.descriptor.out_header body_message_class = ctx.descriptor.out_message # body ctx.out_body_doc = out_body_doc = etree.Element( '{%s}Body' % self.ns_soap_env) # assign raw result to its wrapper, result_message if ctx.descriptor.body_style is BODY_STYLE_WRAPPED: out_type_info = body_message_class._type_info out_object = body_message_class() bm_attrs = self.get_cls_attrs(body_message_class) keys = iter(out_type_info) values = iter(ctx.out_object) while True: try: k = next(keys) except StopIteration: break try: v = next(values) except StopIteration: v = None out_object._safe_set(k, v, body_message_class, bm_attrs) self.to_parent(ctx, body_message_class, out_object, out_body_doc, body_message_class.get_namespace()) else: out_object = ctx.out_object[0] sub_ns = body_message_class.Attributes.sub_ns if sub_ns is None: sub_ns = body_message_class.get_namespace() if sub_ns is DEFAULT_NS: sub_ns = self.app.interface.get_tns() sub_name = body_message_class.Attributes.sub_name if sub_name is None: sub_name = body_message_class.get_type_name() self.to_parent(ctx, body_message_class, out_object, out_body_doc, sub_ns, sub_name) # header if ctx.out_header is not None and header_message_class is not None: ctx.out_header_doc = soap_header_elt = etree.SubElement( ctx.out_document, '{%s}Header' % self.ns_soap_env) if isinstance(ctx.out_header, (list, tuple)): out_headers = ctx.out_header else: out_headers = (ctx.out_header,) for header_class, out_header in zip(header_message_class, out_headers): self.to_parent(ctx, header_class, out_header, soap_header_elt, header_class.get_namespace(), header_class.get_type_name(), ) ctx.out_document.append(ctx.out_body_doc) if self.cleanup_namespaces: etree.cleanup_namespaces(ctx.out_document) self.event_manager.fire_event('after_serialize', ctx) def fault_to_http_response_code(self, fault): return HTTP_500 spyne-spyne-2.14.0/spyne/protocol/soap/soap12.py000066400000000000000000000131111417664205300215150ustar00rootroot00000000000000# # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ The ``spyne.protoco.soap.soap12`` module contains the implementation of a subset of the Soap 1.2 standard. This modules is EXPERIMENTAL. More info can be found at: https://www.w3.org/TR/soap12-part1/ """ import logging from lxml.builder import E from spyne.protocol.soap.soap11 import Soap11 from spyne.protocol.xml import _append from spyne.util.six import string_types from spyne.util.etreeconv import root_dict_to_etree from spyne.const.xml import NS_SOAP12_ENV, NS_XML, PREFMAP logger = logging.getLogger(__name__) logger_invalid = logging.getLogger(__name__ + ".invalid") class Soap12(Soap11): """ The base implementation of a subset of the Soap 1.2 standard. The document is available here: http://www.w3.org/TR/soap12/ """ mime_type = 'application/soap+xml; charset=utf-8' soap_env = PREFMAP[NS_SOAP12_ENV] ns_soap_env = NS_SOAP12_ENV type = set(Soap11.type) type.discard('soap11') type.update(('soap', 'soap12')) def generate_subcode(self, value, subcode=None): subcode_node = E("{%s}Subcode" % self.ns_soap_env) subcode_node.append(E("{%s}Value" % self.ns_soap_env, value)) if subcode: subcode_node.append(subcode) return subcode_node def gen_fault_codes(self, faultstring): faultstrings = faultstring.split('.') value = faultstrings.pop(0) if value == 'Client': value = '%s:Sender' % self.soap_env elif value == 'Server': value = '%s:Receiver' % self.soap_env else: raise TypeError('Wrong fault code, got', type(faultstring)) return value, faultstrings def generate_faultcode(self, element): nsmap = element.nsmap faultcode = [] faultcode.append(element.find('soap:Code/soap:Value', namespaces=nsmap).text) subcode = element.find('soap:Code/soap:Subcode', namespaces=nsmap) while subcode is not None: faultcode.append(subcode.find('soap:Value', namespaces=nsmap).text) subcode = subcode.find('soap:Subcode', namespaces=nsmap) return '.'.join(faultcode) def fault_to_parent(self, ctx, cls, inst, parent, ns, **_): reason = E("{%s}Reason" % self.ns_soap_env) reason.append(E("{%s}Text" % self.ns_soap_env, inst.faultstring, **{'{%s}lang' % NS_XML: inst.lang})) subelts = [ None, # The code tag is put here down the road reason, E("{%s}Role" % self.ns_soap_env, inst.faultactor), ] return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) def _fault_to_parent_impl(self, ctx, cls, inst, parent, ns, subelts, **_): assert isinstance(subelts, list) tag_name = "{%s}Fault" % self.ns_soap_env if isinstance(inst.faultcode, string_types): value, faultcodes = self.gen_fault_codes(inst.faultcode) code = E("{%s}Code" % self.ns_soap_env) code.append(E("{%s}Value" % self.ns_soap_env, value)) child_subcode = False for value in faultcodes[::-1]: if child_subcode: child_subcode = self.generate_subcode(value, child_subcode) else: child_subcode = self.generate_subcode(value) if child_subcode != 0: code.append(child_subcode) subelts[0] = code if isinstance(inst.detail, dict): _append(subelts, E('{%s}Detail' % self.ns_soap_env, root_dict_to_etree(inst.detail))) elif inst.detail is None: pass else: raise TypeError('Fault detail Must be dict, got', type(inst.detail)) return self.gen_members_parent(ctx, cls, inst, parent, tag_name, subelts, add_type=False) def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns, **_): subelts = [ E("{%s}Reason" % self.soap_env, inst.faultstring), E("{%s}Role" % self.soap_env, inst.faultactor), ] return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) def fault_from_element(self, ctx, cls, element): nsmap = element.nsmap code = self.generate_faultcode(element) reason = element.find("soap:Reason/soap:Text", namespaces=nsmap).text.strip() role = element.find("soap:Role", namespaces=nsmap) node = element.find("soap:Node", namespaces=nsmap) detail = element.find("soap:Detail", namespaces=nsmap) faultactor = '' if role is not None: faultactor += role.text.strip() if node is not None: faultactor += node.text.strip() return cls(faultcode=code, faultstring=reason, faultactor=faultactor, detail=detail) spyne-spyne-2.14.0/spyne/protocol/xml.py000066400000000000000000001241001417664205300202470ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.xml`` module contains an xml-based protocol that serializes python objects to xml using Xml Schema conventions. Logs valid documents to ``'spyne.protocol.xml'`` and invalid documents to ``spyne.protocol.xml.invalid``. Use the usual ``logging.getLogger()`` and friends to configure how these get logged. Warning! You can get a lot of crap in the 'invalid' logger. You're not advised to turn it on for a production system. """ import logging logger = logging.getLogger('spyne.protocol.xml') logger_invalid = logging.getLogger('spyne.protocol.xml.invalid') from inspect import isgenerator from collections import defaultdict from lxml import etree from lxml import html from lxml.builder import E from lxml.etree import XMLSyntaxError from lxml.etree import XMLParser from spyne import BODY_STYLE_WRAPPED from spyne.util import Break, coroutine from spyne.util.six import text_type, string_types from spyne.util.cdict import cdict from spyne.util.etreeconv import etree_to_dict, dict_to_etree,\ root_dict_to_etree from spyne.const.xml import XSI, NS_SOAP11_ENC from spyne.error import Fault from spyne.error import ValidationError from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import LIGHT_RED from spyne.const.ansi_color import END_COLOR from spyne.const.xml import NS_SOAP11_ENV from spyne.const.xml import PREFMAP, DEFAULT_NS from spyne.model import Any, ModelBase, Array, Iterable, ComplexModelBase, \ AnyHtml, AnyXml, AnyDict, Unicode, PushBase, File, ByteArray, XmlData, \ XmlAttribute from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.enum import EnumBase from spyne.protocol import ProtocolBase from spyne.util import six if six.PY2: STR_TYPES = (str, unicode) else: STR_TYPES = (str, bytes) NIL_ATTR = {XSI('nil'): 'true'} XSI_TYPE = XSI('type') def _append(parent, child_elt): if hasattr(parent, 'append'): parent.append(child_elt) else: parent.write(child_elt) def _gen_tagname(ns, name): if ns is not None: name = "{%s}%s" % (ns, name) return name class SchemaValidationError(Fault): """Raised when the input stream could not be validated by the Xml Schema.""" CODE = 'Client.SchemaValidationError' def __init__(self, faultstring): super(SchemaValidationError, self).__init__(self.CODE, faultstring) class SubXmlBase(ProtocolBase): def subserialize(self, ctx, cls, inst, parent, ns=None, name=None): return self.to_parent(ctx, cls, inst, parent, name) def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): """Serializes inst to an Element instance and appends it to the 'parent'. :param self: The protocol that will be used to serialize the given value. :param cls: The type of the value that's going to determine how to pack the given value. :param inst: The value to be set for the 'text' element of the newly created SubElement :param parent: The parent Element to which the new child will be appended. :param ns: The target namespace of the new SubElement, used with 'name' to set the tag. :param name: The tag name of the new SubElement, 'retval' by default. """ raise NotImplementedError() class XmlDocument(SubXmlBase): """The Xml input and output protocol, using the information from the Xml Schema generated by Spyne types. See the following material for more (much much more!) information. * http://www.w3.org/TR/xmlschema-0/ * http://www.w3.org/TR/xmlschema-1/ * http://www.w3.org/TR/xmlschema-2/ Receiving Xml from untrusted sources is a dodgy security dance as the Xml attack surface is /huge/. Spyne's ```lxml.etree.XMLParser``` instance has ```resolve_pis```, ```load_dtd```, ```resolve_entities```, ```dtd_validation```, ```huge_tree``` Defaults to ``False`` Having ```resolve_entities``` disabled will prevent the 'lxml' validation for documents with custom xml entities defined in the DTD. See the example in examples/xml/validation_error to play with the settings that work best for you. Please note that enabling ```resolve_entities``` is a security hazard that can lead to disclosure of sensitive information. See https://pypi.python.org/pypi/defusedxml for a pragmatic overview of Xml security in Python world. :param app: The owner application instance. :param validator: One of (None, 'soft', 'lxml', 'schema', ProtocolBase.SOFT_VALIDATION, XmlDocument.SCHEMA_VALIDATION). Both ``'lxml'`` and ``'schema'`` values are equivalent to ``XmlDocument.SCHEMA_VALIDATION``. Defaults to ``None``. :param replace_null_with_default: If ``False``, does not replace incoming explicit null values with denoted default values. This is against Xml Schema standard but consistent with other Spyne protocol implementations. Set this to False if you want cross-protocol compatibility. Defaults to ``True``. Relevant quote from xml schema primer (http://www.w3.org/TR/xmlschema-0/): .. When a value is present and is null The schema processor treats defaulted elements slightly differently. When an element is declared with a default value, the value of the element is whatever value appears as the element's content in the instance document; if the element appears without any content, the schema processor provides the element with a value equal to that of the default attribute. However, if the element does not appear in the instance document, the schema processor does not provide the element at all. In summary, the differences between element and attribute defaults can be stated as: Default attribute values apply when attributes are missing, and default element values apply when elements are empty. :param xml_declaration: Whether to add xml_declaration to the responses Defaults to ``True``. :param cleanup_namespaces: Whether to add clean up namespace declarations in the response document. Defaults to ``True``. :param encoding: The suggested string encoding for the returned xml documents. The transport can override this. Defaults to ``None``. :param pretty_print: When ``True``, returns the document in a pretty-printed format. Defaults to ``False``. :param parse_xsi_type: Set to ``False`` to disable parsing of ``xsi:type`` attribute, effectively disabling polymorphism. Defaults to ``True``. The following are passed straight to the ``XMLParser()`` instance from lxml. Docs are also plagiarized from the lxml documentation. Please note that some of the defaults are different to make parsing safer by default. :param attribute_defaults: read the DTD (if referenced by the document) and add the default attributes from it. Defaults to ``False`` :param dtd_validation: validate while parsing (if a DTD was referenced). Defaults to ``False`` :param load_dtd: load and parse the DTD while parsing (no validation is performed). Defaults to ``False``. :param no_network: prevent network access when looking up external documents. Defaults to ``True``. :param ns_clean: try to clean up redundant namespace declarations. Please note that this is for incoming documents. See ``cleanup_namespaces`` parameter for output documents. Defaults to ``False``. :param recover: try hard to parse through broken Xml. Defaults to ``False``. :param remove_blank_text: discard blank text nodes between tags, also known as ignorable whitespace. This is best used together with a DTD or schema (which tells data and noise apart), otherwise a heuristic will be applied. Defaults to ``False``. :param remove_pis: When ``True`` xml parser discards processing instructions. Defaults to ``True``. :param strip_cdata: replace CDATA sections by normal text content. Defaults to ``True`` :param resolve_entities: replace entities by their text value. Defaults to ``False``. :param huge_tree: disable security restrictions and support very deep trees and very long text content. (only affects libxml2 2.7+) Defaults to ``False``. :param compact: use compact storage for short text content. Defaults to ``True``. """ SCHEMA_VALIDATION = type("Schema", (object,), {}) mime_type = 'text/xml' default_binary_encoding = BINARY_ENCODING_BASE64 type = set(ProtocolBase.type) type.add('xml') soap_env = PREFMAP[NS_SOAP11_ENV] ns_soap_env = NS_SOAP11_ENV ns_soap_enc = NS_SOAP11_ENC def __init__(self, app=None, validator=None, replace_null_with_default=True, xml_declaration=True, cleanup_namespaces=True, encoding=None, pretty_print=False, attribute_defaults=False, dtd_validation=False, load_dtd=False, no_network=True, ns_clean=False, recover=False, remove_blank_text=False, remove_pis=True, strip_cdata=True, resolve_entities=False, huge_tree=False, compact=True, binary_encoding=None, parse_xsi_type=True, polymorphic=False, ): super(XmlDocument, self).__init__(app, validator, binary_encoding=binary_encoding) self.validation_schema = None self.xml_declaration = xml_declaration self.cleanup_namespaces = cleanup_namespaces self.replace_null_with_default = replace_null_with_default if encoding is None: self.encoding = 'UTF-8' else: self.encoding = encoding self.polymorphic = polymorphic self.pretty_print = pretty_print self.parse_xsi_type = parse_xsi_type self.serialization_handlers = cdict({ Any: self.any_to_parent, Fault: self.fault_to_parent, EnumBase: self.enum_to_parent, AnyXml: self.any_xml_to_parent, XmlData: self.xmldata_to_parent, AnyDict: self.any_dict_to_parent, AnyHtml: self.any_html_to_parent, ModelBase: self.modelbase_to_parent, ByteArray: self.byte_array_to_parent, ComplexModelBase: self.complex_to_parent, XmlAttribute: self.xmlattribute_to_parent, SchemaValidationError: self.schema_validation_error_to_parent, }) self.deserialization_handlers = cdict({ AnyHtml: self.html_from_element, AnyXml: self.xml_from_element, Any: self.xml_from_element, Array: self.array_from_element, Fault: self.fault_from_element, AnyDict: self.dict_from_element, EnumBase: self.enum_from_element, ModelBase: self.base_from_element, Unicode: self.unicode_from_element, Iterable: self.iterable_from_element, ByteArray: self.byte_array_from_element, ComplexModelBase: self.complex_from_element, }) self.parser_kwargs = dict( attribute_defaults=attribute_defaults, dtd_validation=dtd_validation, load_dtd=load_dtd, no_network=no_network, ns_clean=ns_clean, recover=recover, remove_blank_text=remove_blank_text, remove_comments=True, remove_pis=remove_pis, strip_cdata=strip_cdata, resolve_entities=resolve_entities, huge_tree=huge_tree, compact=compact, encoding=encoding, ) def set_validator(self, validator): if validator in ('lxml', 'schema') or \ validator is self.SCHEMA_VALIDATION: self.validate_document = self.__validate_lxml self.validator = self.SCHEMA_VALIDATION elif validator == 'soft' or validator is self.SOFT_VALIDATION: self.validator = self.SOFT_VALIDATION elif validator is None: pass else: raise ValueError(validator) self.validation_schema = None def validate_body(self, ctx, message): """Sets ctx.method_request_string and calls :func:`generate_contexts` for validation.""" assert message in (self.REQUEST, self.RESPONSE), message line_header = LIGHT_RED + "Error:" + END_COLOR try: self.validate_document(ctx.in_body_doc) if message is self.REQUEST: line_header = LIGHT_GREEN + "Method request string:" + END_COLOR else: line_header = LIGHT_RED + "Response:" + END_COLOR finally: if logger.level == logging.DEBUG: logger.debug("%s %s" % (line_header, ctx.method_request_string)) logger.debug(etree.tostring(ctx.in_document, pretty_print=True)) def set_app(self, value): ProtocolBase.set_app(self, value) self.validation_schema = None if self.validator is self.SCHEMA_VALIDATION and value is not None: xml_schema = self.app.interface.docs.xml_schema xml_schema.build_validation_schema() self.validation_schema = xml_schema.validation_schema def __validate_lxml(self, payload): ret = self.validation_schema.validate(payload) logger.debug("Validated ? %r" % ret) if ret == False: error_text = text_type(self.validation_schema.error_log.last_error) raise SchemaValidationError(error_text.encode('ascii', 'xmlcharrefreplace')) def create_in_document(self, ctx, charset=None): """Uses the iterable of string fragments in ``ctx.in_string`` to set ``ctx.in_document``.""" string = b''.join(ctx.in_string) try: try: ctx.in_document = etree.fromstring(string, parser=XMLParser(**self.parser_kwargs)) except ValueError: logger.debug('ValueError: Deserializing from unicode strings ' 'with encoding declaration is not supported by ' 'lxml.') ctx.in_document = etree.fromstring(string.decode(charset), self.parser) except XMLSyntaxError as e: logger_invalid.error("%r in string %r", e, string) raise Fault('Client.XMLSyntaxError', str(e)) def decompose_incoming_envelope(self, ctx, message): assert message in (self.REQUEST, self.RESPONSE) ctx.in_header_doc = None # If you need header support, you should use Soap ctx.in_body_doc = ctx.in_document ctx.method_request_string = ctx.in_body_doc.tag self.validate_body(ctx, message) def from_element(self, ctx, cls, element): cls_attrs = self.get_cls_attrs(cls) if bool(element.get(XSI('nil'))): if self.validator is self.SOFT_VALIDATION and not \ cls_attrs.nillable: raise ValidationError(None) if self.replace_null_with_default: return cls_attrs.default return None # if present, use the xsi:type="ns0:ObjectName" # attribute to instantiate subclass objects if self.parse_xsi_type: xsi_type = element.get(XSI_TYPE, None) if xsi_type is not None: if ":" in xsi_type: prefix, objtype = xsi_type.split(':', 1) else: prefix, objtype = None, xsi_type ns = element.nsmap.get(prefix) if ns is not None: classkey = "{%s}%s" % (ns, objtype) else: logger.error("xsi:type namespace prefix " "'%s' in '%s' not found in %r", ns, xsi_type, element.nsmap) raise ValidationError(xsi_type) newclass = ctx.app.interface.classes.get(classkey, None) if newclass is None: logger.error("xsi:type '%s' interpreted as class key '%s' " "is not recognized", xsi_type, classkey) raise ValidationError(xsi_type) cls = newclass logger.debug("xsi:type '%s' overrides %r to %r", xsi_type, cls, newclass) handler = self.deserialization_handlers[cls] return handler(ctx, cls, element) def to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): cls, add_type = self.get_polymorphic_target(cls, inst) cls_attrs = self.get_cls_attrs(cls) subprot = cls_attrs.prot if subprot is not None and isinstance(subprot, SubXmlBase): return subprot.subserialize(ctx, cls, inst, parent, ns, *args, **kwargs) handler = self.serialization_handlers[cls] if inst is None: inst = cls_attrs.default if inst is None: return self.null_to_parent(ctx, cls, inst, parent, ns, *args, **kwargs) if cls_attrs.exc: return kwargs['add_type'] = add_type return handler(ctx, cls, inst, parent, ns, *args, **kwargs) def deserialize(self, ctx, message): """Takes a MethodContext instance and a string containing ONE root xml tag. Returns the corresponding native python object. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_deserialize', ctx) if ctx.descriptor is None: if ctx.in_error is None: raise Fault("Client", "Method %r not found." % ctx.method_request_string) else: raise ctx.in_error if message is self.REQUEST: body_class = ctx.descriptor.in_message elif message is self.RESPONSE: body_class = ctx.descriptor.out_message # decode method arguments if ctx.in_body_doc is None: ctx.in_object = [None] * len(body_class._type_info) else: ctx.in_object = self.from_element(ctx, body_class, ctx.in_body_doc) if logger.level == logging.DEBUG and message is self.REQUEST: line_header = '%sRequest%s' % (LIGHT_GREEN, END_COLOR) outdoc_str = None if ctx.out_document is not None: outdoc_str = etree.tostring(ctx.out_document, xml_declaration=self.xml_declaration, pretty_print=True) logger.debug("%s %s" % (line_header, outdoc_str)) self.event_manager.fire_event('after_deserialize', ctx) def serialize(self, ctx, message): """Uses ``ctx.out_object``, ``ctx.out_header`` or ``ctx.out_error`` to set ``ctx.out_body_doc``, ``ctx.out_header_doc`` and ``ctx.out_document`` as an ``lxml.etree._Element instance``. Not meant to be overridden. """ assert message in (self.REQUEST, self.RESPONSE) self.event_manager.fire_event('before_serialize', ctx) if ctx.out_error is not None: tmp_elt = etree.Element('punk') retval = self.to_parent(ctx, ctx.out_error.__class__, ctx.out_error, tmp_elt, self.app.interface.get_tns()) ctx.out_document = tmp_elt[0] else: if message is self.REQUEST: result_message_class = ctx.descriptor.in_message elif message is self.RESPONSE: result_message_class = ctx.descriptor.out_message # assign raw result to its wrapper, result_message if ctx.descriptor.body_style == BODY_STYLE_WRAPPED: result_inst = result_message_class() for i, (k, v) in enumerate( result_message_class._type_info.items()): attrs = self.get_cls_attrs(v) result_inst._safe_set(k, ctx.out_object[i], v, attrs) else: result_inst = ctx.out_object if ctx.out_stream is None: tmp_elt = etree.Element('punk') retval = self.to_parent(ctx, result_message_class, result_inst, tmp_elt, self.app.interface.get_tns()) ctx.out_document = tmp_elt[0] else: retval = self.incgen(ctx, result_message_class, result_inst, self.app.interface.get_tns()) if self.cleanup_namespaces and ctx.out_document is not None: etree.cleanup_namespaces(ctx.out_document) self.event_manager.fire_event('after_serialize', ctx) return retval def create_out_string(self, ctx, charset=None): """Sets an iterable of string fragments to ctx.out_string""" if charset is None: charset = self.encoding ctx.out_string = [etree.tostring(ctx.out_document, encoding=charset, pretty_print=self.pretty_print, xml_declaration=self.xml_declaration)] if logger.level == logging.DEBUG: logger.debug('%sResponse%s %s' % (LIGHT_RED, END_COLOR, etree.tostring(ctx.out_document, pretty_print=True, encoding='UTF-8'))) @coroutine def incgen(self, ctx, cls, inst, ns, name=None): if name is None: name = cls.get_type_name() with etree.xmlfile(ctx.out_stream) as xf: ret = self.to_parent(ctx, cls, inst, xf, ns, name) if isgenerator(ret): try: while True: y = (yield) # may throw Break ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass if hasattr(ctx.out_stream, 'finish'): ctx.out_stream.finish() def _gen_tag(self, cls, ns, name, add_type=False, **_): if ns is not None: name = "{%s}%s" % (ns, name) retval = E(name) if add_type: retval.attrib[XSI_TYPE] = cls.get_type_name_ns(self.app.interface) return retval def byte_array_to_parent(self, ctx, cls, inst, parent, ns, name='retval', **kwargs): elt = self._gen_tag(cls, ns, name, **kwargs) elt.text = self.to_unicode(cls, inst, self.binary_encoding) _append(parent, elt) def modelbase_to_parent(self, ctx, cls, inst, parent, ns, name='retval', **kwargs): elt = self._gen_tag(cls, ns, name, **kwargs) elt.text = self.to_unicode(cls, inst) _append(parent, elt) def null_to_parent(self, ctx, cls, inst, parent, ns, name='retval', **kwargs): if issubclass(cls, XmlAttribute): return elif issubclass(cls, XmlData): parent.attrib.update(NIL_ATTR) else: elt = self._gen_tag(cls, ns, name, **kwargs) elt.attrib.update(NIL_ATTR) _append(parent, elt) def null_from_element(self, ctx, cls, element): return None def xmldata_to_parent(self, ctx, cls, inst, parent, ns, name, add_type=False, **_): cls_attrs = self.get_cls_attrs(cls) ns = cls._ns if ns is None: ns = cls_attrs.sub_ns name = _gen_tagname(ns, name) if add_type: parent.attrib[XSI_TYPE] = cls.get_type_name_ns(self.app.interface) cls.marshall(self, name, inst, parent) def xmlattribute_to_parent(self, ctx, cls, inst, parent, ns, name, **_): ns = cls._ns cls_attrs = self.get_cls_attrs(cls) if ns is None: ns = cls_attrs.sub_ns name = _gen_tagname(ns, name) if inst is not None: if issubclass(cls.type, (ByteArray, File)): parent.set(name, self.to_unicode(cls.type, inst, self.binary_encoding)) else: parent.set(name, self.to_unicode(cls.type, inst)) @coroutine def gen_members_parent(self, ctx, cls, inst, parent, tag_name, subelts, add_type): attrib = {} if add_type: tnn = cls.get_type_name_ns(self.app.interface) if tnn != None: attrib[XSI_TYPE] = tnn else: # this only happens on incomplete interface states for eg. # get_object_as_xml where the full init is not performed for # perf reasons attrib[XSI_TYPE] = cls.get_type_name() if isinstance(parent, etree._Element): elt = etree.SubElement(parent, tag_name, attrib=attrib) elt.extend(subelts) ret = self._get_members_etree(ctx, cls, inst, elt) if isgenerator(ret): try: while True: y = (yield) # may throw Break ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass else: with parent.element(tag_name, attrib=attrib): for e in subelts: parent.write(e) ret = self._get_members_etree(ctx, cls, inst, parent) if isgenerator(ret): try: while True: y = (yield) ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass @coroutine def _get_members_etree(self, ctx, cls, inst, parent): try: parent_cls = getattr(cls, '__extends__', None) if not (parent_cls is None): ret = self._get_members_etree(ctx, parent_cls, inst, parent) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass for k, v in cls._type_info.items(): sub_cls_attrs = self.get_cls_attrs(v) if sub_cls_attrs.exc: continue try: subvalue = getattr(inst, k, None) except: # e.g. SqlAlchemy could throw NoSuchColumnError subvalue = None # This is a tight loop, so enable this only when necessary. # logger.debug("get %r(%r) from %r: %r" % (k, v, inst, subvalue)) sub_ns = v.Attributes.sub_ns if sub_ns is None: sub_ns = cls.get_namespace() sub_name = v.Attributes.sub_name if sub_name is None: sub_name = k mo = v.Attributes.max_occurs if subvalue is not None and mo > 1: if isinstance(subvalue, PushBase): while True: sv = (yield) ret = self.to_parent(ctx, v, sv, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass else: for sv in subvalue: ret = self.to_parent(ctx, v, sv, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) # may throw Break ret.send(sv2) except Break: try: ret.throw(Break()) except StopIteration: pass # Don't include empty values for # non-nillable optional attributes. elif subvalue is not None or v.Attributes.min_occurs > 0: ret = self.to_parent(ctx, v, subvalue, parent, sub_ns, sub_name) if ret is not None: try: while True: sv2 = (yield) ret.send(sv2) except Break as b: try: ret.throw(b) except StopIteration: pass except Break: pass def complex_to_parent(self, ctx, cls, inst, parent, ns, name=None, add_type=False, **_): cls_attrs = self.get_cls_attrs(cls) sub_name = cls_attrs.sub_name if sub_name is not None: name = sub_name if name is None: name = cls.get_type_name() sub_ns = cls_attrs.sub_ns if not sub_ns in (None, DEFAULT_NS): ns = sub_ns tag_name = _gen_tagname(ns, name) inst = cls.get_serialization_instance(inst) return self.gen_members_parent(ctx, cls, inst, parent, tag_name, [], add_type) def _fault_to_parent_impl(self, ctx, cls, inst, parent, ns, subelts, **_): tag_name = "{%s}Fault" % self.ns_soap_env # Accepting raw lxml objects as detail is DEPRECATED. It's also not # documented. It's kept for backwards-compatibility purposes. if isinstance(inst.detail, string_types + (etree._Element,)): _append(subelts, E('detail', inst.detail)) elif isinstance(inst.detail, dict): if len(inst.detail) > 0: _append(subelts, root_dict_to_etree({'detail':inst.detail})) elif inst.detail is None: pass else: raise TypeError('Fault detail Must be dict, got', type(inst.detail)) # add other nonstandard fault subelements with get_members_etree return self.gen_members_parent(ctx, cls, inst, parent, tag_name, subelts, add_type=False) def fault_to_parent(self, ctx, cls, inst, parent, ns, *args, **kwargs): subelts = [ E("faultcode", '%s:%s' % (self.soap_env, inst.faultcode)), E("faultstring", inst.faultstring), E("faultactor", inst.faultactor), ] return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) def schema_validation_error_to_parent(self, ctx, cls, inst, parent, ns,**_): subelts = [ E("faultcode", '%s:%s' % (self.soap_env, inst.faultcode)), # HACK: Does anyone know a better way of injecting raw xml entities? E("faultstring", html.fromstring(inst.faultstring).text), E("faultactor", inst.faultactor), ] if inst.detail != None: _append(subelts, E('detail', inst.detail)) # add other nonstandard fault subelements with get_members_etree return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts) def enum_to_parent(self, ctx, cls, inst, parent, ns, name='retval', **kwargs): self.modelbase_to_parent(ctx, cls, str(inst), parent, ns, name, **kwargs) def any_xml_to_parent(self, ctx, cls, inst, parent, ns, name, **_): if isinstance(inst, STR_TYPES): inst = etree.fromstring(inst) _append(parent, E(_gen_tagname(ns, name), inst)) def any_to_parent(self, ctx, cls, inst, parent, ns, name, **_): _append(parent, E(_gen_tagname(ns, name), inst)) def any_html_to_parent(self, ctx, cls, inst, parent, ns, name, **_): if isinstance(inst, string_types) and len(inst) > 0: inst = html.fromstring(inst) _append(parent, E(_gen_tagname(ns, name), inst)) def any_dict_to_parent(self, ctx, cls, inst, parent, ns, name, **_): elt = E(_gen_tagname(ns, name)) dict_to_etree(inst, elt) _append(parent, elt) def complex_from_element(self, ctx, cls, elt): inst = cls.get_deserialization_instance(ctx) flat_type_info = cls.get_flat_type_info(cls) # this is for validating cls.Attributes.{min,max}_occurs frequencies = defaultdict(int) cls_attrs = self.get_cls_attrs(cls) if cls_attrs._xml_tag_body_as is not None: for xtba_key, xtba_type in cls_attrs._xml_tag_body_as: xtba_attrs = self.get_cls_attrs(xtba_type.type) if issubclass(xtba_type.type, (ByteArray, File)): value = self.from_unicode(xtba_type.type, elt.text, self.binary_encoding) else: value = self.from_unicode(xtba_type.type, elt.text) inst._safe_set(xtba_key, value, xtba_type.type, xtba_attrs) # parse input to set incoming data to related attributes. for c in elt: if isinstance(c, etree._Comment): continue key = c.tag.split('}', 1)[-1] frequencies[key] += 1 member = flat_type_info.get(key, None) if member is None: member, key = cls._type_info_alt.get(key, (None, key)) if member is None: member, key = cls._type_info_alt.get(c.tag, (None, key)) if member is None: continue member_attrs = self.get_cls_attrs(member) mo = member_attrs.max_occurs if mo > 1: value = getattr(inst, key, None) if value is None: value = [] value.append(self.from_element(ctx, member, c)) else: value = self.from_element(ctx, member, c) inst._safe_set(key, value, member, member_attrs) for key, value_str in c.attrib.items(): submember = flat_type_info.get(key, None) if submember is None: submember, key = cls._type_info_alt.get(key, (None, key)) if submember is None: continue submember_attrs = self.get_cls_attrs(submember) mo = submember_attrs.max_occurs if mo > 1: value = getattr(inst, key, None) if value is None: value = [] value.append(self.from_unicode(submember.type, value_str)) else: value = self.from_unicode(submember.type, value_str) inst._safe_set(key, value, submember.type, submember_attrs) for key, value_str in elt.attrib.items(): member = flat_type_info.get(key, None) if member is None: member, key = cls._type_info_alt.get(key, (None, key)) if member is None: continue if not issubclass(member, XmlAttribute): continue if issubclass(member.type, (ByteArray, File)): value = self.from_unicode(member.type, value_str, self.binary_encoding) else: value = self.from_unicode(member.type, value_str) member_attrs = self.get_cls_attrs(member.type) inst._safe_set(key, value, member.type, member_attrs) if self.validator is self.SOFT_VALIDATION: for key, c in flat_type_info.items(): val = frequencies.get(key, 0) attr = self.get_cls_attrs(c) if val < attr.min_occurs or val > attr.max_occurs: raise Fault('Client.ValidationError', '%r member does not ' 'respect frequency constraints.' % key) return inst def array_from_element(self, ctx, cls, element): retval = [ ] (serializer,) = cls._type_info.values() for child in element.getchildren(): retval.append(self.from_element(ctx, serializer, child)) return retval def iterable_from_element(self, ctx, cls, element): (serializer,) = cls._type_info.values() for child in element.getchildren(): yield self.from_element(ctx, serializer, child) def enum_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) return getattr(cls, element.text) def fault_from_element(self, ctx, cls, element): code = element.find('faultcode').text string = element.find('faultstring').text factor = element.find('faultactor') if factor is not None: factor = factor.text detail = element.find('detail') return cls(faultcode=code, faultstring=string, faultactor=factor, detail=detail) def xml_from_element(self, ctx, cls, element): children = element.getchildren() retval = None if children: retval = element.getchildren()[0] return retval def html_from_element(self, ctx, cls, element): children = element.getchildren() retval = None if len(children) == 1: retval = children[0] # this is actually a workaround to a case that should never exist -- # anyXml types should only have one child tag. elif len(children) > 1: retval = E.html(*children) return retval def dict_from_element(self, ctx, cls, element): children = element.getchildren() if children: return etree_to_dict(element) return None def unicode_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) s = element.text if s is None: s = '' retval = self.from_unicode(cls, s) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval def base_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) retval = self.from_unicode(cls, element.text) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval def byte_array_from_element(self, ctx, cls, element): if self.validator is self.SOFT_VALIDATION and not ( cls.validate_string(cls, element.text)): raise ValidationError(element.text) retval = self.from_unicode(cls, element.text, self.binary_encoding) if self.validator is self.SOFT_VALIDATION and not ( cls.validate_native(cls, retval)): raise ValidationError(retval) return retval spyne-spyne-2.14.0/spyne/protocol/yaml.py000066400000000000000000000142451417664205300204210ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.protocol.yaml`` package contains the Yaml-related protocols. Currently, only :class:`spyne.protocol.yaml.YamlDocument` is supported. Initially released in 2.10.0-rc. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from spyne import ValidationError from spyne.util import six from spyne.model.binary import BINARY_ENCODING_BASE64 from spyne.model.primitive import Boolean from spyne.model.primitive import Integer from spyne.model.primitive import Double from spyne.model.fault import Fault from spyne.protocol.dictdoc import HierDictDocument import yaml from yaml.parser import ParserError try: from yaml import CLoader as Loader from yaml import CDumper as Dumper from yaml import CSafeLoader as SafeLoader from yaml import CSafeDumper as SafeDumper except ImportError: from yaml import Loader from yaml import Dumper from yaml import SafeLoader from yaml import SafeDumper NON_NUMBER_TYPES = tuple({list, dict, six.text_type, six.binary_type}) class YamlDocument(HierDictDocument): """An implementation of the Yaml protocol that uses the PyYaml package. See ProtocolBase ctor docstring for its arguments. Yaml-specific arguments follow: :param safe: Use ``safe_dump`` instead of ``dump`` and ``safe_load`` instead of ``load``. This is not a security feature, search for 'safe_dump' in http://www.pyyaml.org/wiki/PyYAMLDocumentation :param kwargs: See the yaml documentation in ``load, ``safe_load``, ``dump`` or ``safe_dump`` depending on whether you use yaml as an input or output protocol. For the output case, Spyne sets ``default_flow_style=False`` and ``indent=4`` by default. """ mime_type = 'text/yaml' type = set(HierDictDocument.type) type.add('yaml') text_based = True default_binary_encoding = BINARY_ENCODING_BASE64 # for test classes _decimal_as_string = True def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False, polymorphic=False, # YamlDocument specific safe=True, encoding='UTF-8', allow_unicode=True, **kwargs): super(YamlDocument, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered, polymorphic) self._from_unicode_handlers[Double] = self._ret_number self._from_unicode_handlers[Boolean] = self._ret_bool self._from_unicode_handlers[Integer] = self._ret_number self._to_unicode_handlers[Double] = self._ret self._to_unicode_handlers[Boolean] = self._ret self._to_unicode_handlers[Integer] = self._ret loader = Loader dumper = Dumper if safe: loader = SafeLoader dumper = SafeDumper self.in_kwargs = dict(kwargs) self.out_kwargs = dict(kwargs) self.in_kwargs['Loader'] = loader self.out_kwargs['Dumper'] = dumper loader.add_constructor('tag:yaml.org,2002:python/unicode', _unicode_loader) self.out_kwargs['encoding'] = encoding self.out_kwargs['allow_unicode'] = allow_unicode if not 'indent' in self.out_kwargs: self.out_kwargs['indent'] = 4 if not 'default_flow_style' in self.out_kwargs: self.out_kwargs['default_flow_style'] = False def _ret(self, _, value): return value def _ret_number(self, _, value): if isinstance(value, NON_NUMBER_TYPES): raise ValidationError(value) if value in (True, False): return int(value) return value def _ret_bool(self, _, value): if value is None or value in (True, False): return value raise ValidationError(value) def create_in_document(self, ctx, in_string_encoding=None): """Sets ``ctx.in_document`` using ``ctx.in_string``.""" if in_string_encoding is None: in_string_encoding = 'UTF-8' try: try: s = b''.join(ctx.in_string).decode(in_string_encoding) except TypeError: s = ''.join(ctx.in_string) ctx.in_document = yaml.load(s, **self.in_kwargs) except ParserError as e: raise Fault('Client.YamlDecodeError', repr(e)) def create_out_string(self, ctx, out_string_encoding='utf8'): """Sets ``ctx.out_string`` using ``ctx.out_document``.""" ctx.out_string = (yaml.dump(o, **self.out_kwargs) for o in ctx.out_document) if six.PY2 and out_string_encoding is not None: ctx.out_string = ( yaml.dump(o, **self.out_kwargs).encode(out_string_encoding) for o in ctx.out_document) def _unicode_loader(loader, node): return node.value def _decimal_to_bytes(): pass def _decimal_from_bytes(): pass spyne-spyne-2.14.0/spyne/server/000077500000000000000000000000001417664205300165445ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/server/__init__.py000066400000000000000000000016361417664205300206630ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server`` package contains the server transports.""" from spyne.server._base import ServerBase from spyne.server.null import NullServer spyne-spyne-2.14.0/spyne/server/_base.py000066400000000000000000000213671417664205300202000ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from inspect import isgenerator from spyne import EventManager, Ignored from spyne.auxproc import process_contexts from spyne.model import Fault, PushBase from spyne.protocol import ProtocolBase from spyne.util import Break, coroutine class ServerBase(object): """This class is the abstract base class for all server transport implementations. Unlike the client transports, this class does not define a pure-virtual method that needs to be implemented by all base classes. If there needs to be a call to start the main loop, it's called ``serve_forever()`` by convention. """ transport = None """The transport type, which is a URI string to its definition by convention.""" def __init__(self, app): self.app = app self.app.transport = self.transport # FIXME: this is weird self.appinit() self.event_manager = EventManager(self) @property def doc(self): # for backwards compatibility return self.app.interface.docs @doc.setter def doc(self, value): # for backwards compatibility self.app.interface.docs = doc def appinit(self): self.app.reinitialize(self) def generate_contexts(self, ctx, in_string_charset=None): """Calls create_in_document and decompose_incoming_envelope to get method_request string in order to generate contexts. """ try: # sets ctx.in_document self.app.in_protocol.create_in_document(ctx, in_string_charset) # sets ctx.in_body_doc, ctx.in_header_doc and # ctx.method_request_string self.app.in_protocol.decompose_incoming_envelope(ctx, ProtocolBase.REQUEST) # returns a list of contexts. multiple contexts can be returned # when the requested method also has bound auxiliary methods. retval = self.app.in_protocol.generate_method_contexts(ctx) except Fault as e: ctx.in_object = None ctx.in_error = e ctx.out_error = e retval = (ctx,) ctx.fire_event('method_exception_object') return retval def get_in_object(self, ctx): """Uses the ``ctx.in_string`` to set ``ctx.in_body_doc``, which in turn is used to set ``ctx.in_object``.""" try: # sets ctx.in_object and ctx.in_header self.app.in_protocol.deserialize(ctx, message=self.app.in_protocol.REQUEST) except Fault as e: logger.exception(e) logger.debug("Failed document is: %s", ctx.in_document) ctx.in_object = None ctx.in_error = e ctx.out_error = e ctx.fire_event('method_exception_object') def get_out_object(self, ctx): """Calls the matched user function by passing it the ``ctx.in_object`` to set ``ctx.out_object``.""" if ctx.in_error is None: # event firing is done in the spyne.application.Application self.app.process_request(ctx) else: raise ctx.in_error if isinstance(ctx.out_object, (list, tuple)) \ and len(ctx.out_object) > 0 \ and isinstance(ctx.out_object[0], Ignored): ctx.out_object = (None,) elif isinstance(ctx.out_object, Ignored): ctx.out_object = () def convert_pull_to_push(self, ctx, gen): oobj, = ctx.out_object if oobj is None: gen.throw(Break()) elif isinstance(oobj, PushBase): pass elif len(ctx.pusher_stack) > 0: oobj = ctx.pusher_stack[-1] assert isinstance(oobj, PushBase) else: raise ValueError("%r is not a PushBase instance" % oobj) retval = self.init_interim_push(oobj, ctx, gen) return self.pusher_try_close(ctx, oobj, retval) def get_out_string_pull(self, ctx): """Uses the ``ctx.out_object`` to set ``ctx.out_document`` and later ``ctx.out_string``.""" # This means the user wanted to override the way Spyne generates the # outgoing byte stream. So we leave it alone. if ctx.out_string is not None: return if ctx.out_document is None: ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) if isgenerator(ret) and ctx.out_object is not None and \ len(ctx.out_object) == 1: if len(ctx.pusher_stack) > 0: # we suspend request processing here because there now # seems to be a PushBase waiting for input. return self.convert_pull_to_push(ctx, ret) self.finalize_context(ctx) def finalize_context(self, ctx): if ctx.out_error is None: ctx.fire_event('method_return_document') else: ctx.fire_event('method_exception_document') ctx.out_protocol.create_out_string(ctx) if ctx.out_error is None: ctx.fire_event('method_return_string') else: ctx.fire_event('method_exception_string') if ctx.out_string is None: ctx.out_string = (b'',) # for backwards compatibility get_out_string = get_out_string_pull @coroutine def get_out_string_push(self, ctx): """Uses the ``ctx.out_object`` to directly set ``ctx.out_string``.""" ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE) if isgenerator(ret): try: while True: y = (yield) ret.send(y) except Break: try: ret.throw(Break()) except StopIteration: pass self.finalize_context(ctx) def serve_forever(self): """Implement your event loop here, if needed.""" raise NotImplementedError() def init_interim_push(self, ret, p_ctx, gen): assert isinstance(ret, PushBase) assert p_ctx.out_stream is not None # we don't add interim pushers to the stack because we don't know # where to find them in the out_object's hierarchy. whatever finds # one in the serialization pipeline has to push it to pusher_stack so # the machinery in ServerBase can initialize them using this function. # fire events p_ctx.fire_event('method_return_push') def _cb_push_finish(): process_contexts(self, (), p_ctx) return self.pusher_init(p_ctx, gen, _cb_push_finish, ret, interim=True) def pusher_init(self, p_ctx, gen, _cb_push_finish, pusher, interim): return pusher.init(p_ctx, gen, _cb_push_finish, None, interim) def pusher_try_close(self, ctx, pusher, _): logger.debug("Closing pusher with ret=%r", pusher) pusher.close() popped = ctx.pusher_stack.pop() assert popped is pusher def init_root_push(self, ret, p_ctx, others): assert isinstance(ret, PushBase) if ret in p_ctx.pusher_stack: logger.warning('PushBase reinit avoided.') return p_ctx.pusher_stack.append(ret) # fire events p_ctx.fire_event('method_return_push') # start push serialization gen = self.get_out_string_push(p_ctx) assert isgenerator(gen), "It looks like this protocol is not " \ "async-compliant yet." def _cb_push_finish(): process_contexts(self, others, p_ctx) retval = self.pusher_init(p_ctx, gen, _cb_push_finish, ret, interim=False) self.pusher_try_close(p_ctx, ret, retval) return retval @staticmethod def set_out_document_push(ctx): ctx.out_document = _write() ctx.out_document.send(None) def _write(): v = yield yield v spyne-spyne-2.14.0/spyne/server/django.py000066400000000000000000000326641417664205300203730ustar00rootroot00000000000000# encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.django`` module contains a Django-compatible Http transport. It's a thin wrapper around :class:`spyne.server.wsgi.WsgiApplication`. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from functools import update_wrapper from spyne import Address from spyne.application import get_fault_string_from_exception, Application from spyne.auxproc import process_contexts from spyne.model.fault import Fault from spyne.protocol.soap import Soap11 from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase, HttpMethodContext, HttpTransportContext from spyne.server.wsgi import WsgiApplication from spyne.util import _bytes_join from spyne.util.address import address_parser from django.http import HttpResponse, HttpResponseNotAllowed, Http404 from django.views.decorators.csrf import csrf_exempt try: from django.http import StreamingHttpResponse except ImportError as _import_error: _local_import_error = _import_error def StreamingHttpResponse(*args, **kwargs): raise _local_import_error class DjangoApplication(WsgiApplication): """You should use this for regular RPC.""" HttpResponseObject = HttpResponse # noinspection PyMethodOverriding # because this is VERY similar to a Wsgi app # but not that much. def __call__(self, request): retval = self.HttpResponseObject() def start_response(status, headers): # Status is one of spyne.const.http status, reason = status.split(' ', 1) retval.status_code = int(status) for header, value in headers: retval[header] = value environ = request.META.copy() # FIXME: No idea what these two did. # They were commented out to fix compatibility issues with # Django-1.2.x # See http://github.com/arskom/spyne/issues/222. # If you don't override wsgi.input django and spyne will read # the same buffer twice. If django read whole buffer spyne # would hang waiting for extra request data. Use DjangoServer instead # of monkeypatching wsgi.inpu. #environ['wsgi.input'] = request #environ['wsgi.multithread'] = False response = WsgiApplication.__call__(self, environ, start_response) self.set_response(retval, response) return retval def set_response(self, retval, response): retval.content = _bytes_join(response, b"") class StreamingDjangoApplication(DjangoApplication): """You should use this when you're generating HUGE data as response. New in Django 1.5. """ HttpResponseObject = StreamingHttpResponse def set_response(self, retval, response): retval.streaming_content = response class DjangoHttpTransportContext(HttpTransportContext): def get_path(self): return self.req.path def get_request_method(self): return self.req.method def get_request_content_type(self): return self.req.META['CONTENT_TYPE'] def get_path_and_qs(self): return self.req.get_full_path() def get_cookie(self, key): return self.req.COOKIES[key] def get_peer(self): addr, port = address_parser.get_ip(self.req.META),\ address_parser.get_port(self.req.META) if address_parser.is_valid_ipv4(addr, port): return Address(type=Address.TCP4, host=addr, port=port) if address_parser.is_valid_ipv6(addr, port): return Address(type=Address.TCP6, host=addr, port=port) class DjangoHttpMethodContext(HttpMethodContext): HttpTransportContext = DjangoHttpTransportContext class DjangoServer(HttpBase): """Server talking in Django request/response objects.""" def __init__(self, app, chunked=False, cache_wsdl=True): super(DjangoServer, self).__init__(app, chunked=chunked) self._wsdl = None self._cache_wsdl = cache_wsdl def handle_rpc(self, request, *args, **kwargs): """Handle rpc request. :params request: Django HttpRequest instance. :returns: HttpResponse instance. """ contexts = self.get_contexts(request) p_ctx, others = contexts[0], contexts[1:] # TODO: Rate limiting p_ctx.active = True if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error) try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) return self.handle_error(p_ctx, others, p_ctx.out_error) have_protocol_headers = (isinstance(p_ctx.out_protocol, HttpRpc) and p_ctx.out_header_doc is not None) if have_protocol_headers: p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) if p_ctx.descriptor and p_ctx.descriptor.mtom: raise NotImplementedError if self.chunked: response = StreamingHttpResponse(p_ctx.out_string) else: response = HttpResponse(b''.join(p_ctx.out_string)) return self.response(response, p_ctx, others) def handle_wsdl(self, request, *args, **kwargs): """Return services WSDL.""" ctx = HttpMethodContext(self, request, 'text/xml; charset=utf-8') if self.doc.wsdl11 is None: raise Http404('WSDL is not available') if self._wsdl is None: # Interface document building is not thread safe so we don't use # server interface document shared between threads. Instead we # create and build interface documents in current thread. This # section can be safely repeated in another concurrent thread. self.doc.wsdl11.service_elt_dict = {} self.doc.wsdl11.build_interface_document(request.build_absolute_uri()) wsdl = self.doc.wsdl11.get_interface_document() if self._cache_wsdl: self._wsdl = wsdl else: wsdl = self._wsdl ctx.transport.wsdl = wsdl response = HttpResponse(ctx.transport.wsdl) return self.response(response, ctx, ()) def handle_error(self, p_ctx, others, error): """Serialize errors to an iterable of strings and return them. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. """ if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = \ p_ctx.out_protocol.fault_to_http_response_code(error) self.get_out_string(p_ctx) resp = HttpResponse(b''.join(p_ctx.out_string)) return self.response(resp, p_ctx, others, error) def get_contexts(self, request): """Generate contexts for rpc request. :param request: Django HttpRequest instance. :returns: generated contexts """ initial_ctx = DjangoHttpMethodContext(self, request, self.app.out_protocol.mime_type) initial_ctx.in_string = [request.body] return self.generate_contexts(initial_ctx) def response(self, response, p_ctx, others, error=None): """Populate response with transport headers and finalize it. :param response: Django HttpResponse. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. :returns: Django HttpResponse """ for h, v in p_ctx.transport.resp_headers.items(): if v is not None: response[h] = v if p_ctx.transport.resp_code: response.status_code = int(p_ctx.transport.resp_code[:3]) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) p_ctx.close() return response class DjangoView(object): """Represent spyne service as Django class based view.""" application = None server = None services = () tns = 'spyne.application' name = 'Application' in_protocol = Soap11(validator='lxml') out_protocol = Soap11() interface = None chunked = False cache_wsdl = True http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, server, **kwargs): self.server = server for key, value in kwargs.items(): setattr(self, key, value) @classmethod def as_view(cls, **initkwargs): """Register application, server and create new view. :returns: callable view function """ # sanitize keyword arguments for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def get(key): value = initkwargs.get(key) return value if value is not None else getattr(cls, key) def pop(key): value = initkwargs.pop(key, None) return value if value is not None else getattr(cls, key) application = get('application') or Application( services=get('services'), tns=get('tns'), name=get('name'), in_protocol=get('in_protocol'), out_protocol=get('out_protocol'), ) server = pop('server') or DjangoServer(application, chunked=get('chunked'), cache_wsdl=get('cache_wsdl')) def view(request, *args, **kwargs): self = cls(server=server, **initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view @csrf_exempt def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) def get(self, request, *args, **kwargs): return self.server.handle_wsdl(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.server.handle_rpc(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): logger.warning('Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': self.request}) return HttpResponseNotAllowed(self._allowed_methods()) def options(self, request, *args, **kwargs): """Handle responding to requests for the OPTIONS HTTP verb.""" response = HttpResponse() response['Allow'] = ', '.join(self._allowed_methods()) response['Content-Length'] = '0' return response def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] spyne-spyne-2.14.0/spyne/server/http.py000066400000000000000000000267021417664205300201040ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from collections import defaultdict from email import utils from email.utils import encode_rfc2231 from email.message import tspecials from spyne import TransportContext, MethodDescriptor, MethodContext, Redirect from spyne.server import ServerBase from spyne.protocol.http import HttpPattern from spyne.const.http import gen_body_redirect, HTTP_301, HTTP_302, HTTP_303, \ HTTP_307 class HttpRedirect(Redirect): def __init__(self, ctx, location, orig_exc=None, code=HTTP_302): super(HttpRedirect, self) \ .__init__(ctx, location, orig_exc=orig_exc) self.ctx = ctx self.location = location self.orig_exc = orig_exc self.code = code def do_redirect(self): if not isinstance(self.ctx.transport, HttpTransportContext): if self.orig_exc is not None: raise self.orig_exc raise TypeError(self.ctx.transport) self.ctx.transport.respond(self.code, location=self.location) # # Plagiarized HttpTransport.add_header() and _formatparam() function from # Python 2.7 stdlib. # # Copyright (C) 2001-2007 Python Software Foundation # Author: Barry Warsaw # Contact: email-sig@python.org # def _formatparam(param, value=None, quote=True): """Convenience function to format and return a key=value pair. This will quote the value if needed or if quote is true. If value is a three tuple (charset, language, value), it will be encoded according to RFC2231 rules. If it contains non-ascii characters it will likewise be encoded according to RFC2231 rules, using the utf-8 charset and a null language. """ if value is None or len(value) == 0: return param # A tuple is used for RFC 2231 encoded parameter values where items # are (charset, language, value). charset is a string, not a Charset # instance. RFC 2231 encoded values are never quoted, per RFC. if isinstance(value, tuple): # Encode as per RFC 2231 param += '*' value = encode_rfc2231(value[2], value[0], value[1]) return '%s=%s' % (param, value) try: value.encode('ascii') except UnicodeEncodeError: param += '*' value = encode_rfc2231(value, 'utf-8', '') return '%s=%s' % (param, value) # BAW: Please check this. I think that if quote is set it should # force quoting even if not necessary. if quote or tspecials.search(value): return '%s="%s"' % (param, utils.quote(value)) return '%s=%s' % (param, value) class HttpTransportContext(TransportContext): """The abstract base class that is used in the transport attribute of the :class:`HttpMethodContext` class and its subclasses.""" def __init__(self, parent, transport, request, content_type): super(HttpTransportContext, self).__init__(parent, transport, 'http') self.req = request """HTTP Request. This is transport-specific""" self.resp_headers = {} """HTTP Response headers.""" self.mime_type = content_type self.resp_code = None """HTTP Response code.""" self.wsdl = None """The WSDL document that is being returned. Only relevant when handling WSDL requests.""" self.wsdl_error = None """The error when handling WSDL requests.""" def get_mime_type(self): return self.resp_headers.get('Content-Type', None) def set_mime_type(self, what): self.resp_headers['Content-Type'] = what def respond(self, resp_code, **kwargs): self.resp_code = resp_code if resp_code in (HTTP_301, HTTP_302, HTTP_303, HTTP_307): l = kwargs.pop('location') self.resp_headers['Location'] = l self.parent.out_string = [gen_body_redirect(resp_code, l)] self.mime_type = 'text/html' else: # So that deserialization is skipped. self.parent.out_string = [] def get_url(self): raise NotImplementedError() def get_path(self): raise NotImplementedError() def get_request_method(self): raise NotImplementedError() def get_request_content_type(self): raise NotImplementedError() def get_path_and_qs(self): raise NotImplementedError() def get_cookie(self, key): raise NotImplementedError() def get_peer(self): raise NotImplementedError() @staticmethod def gen_header(_value, **kwargs): parts = [] for k, v in kwargs.items(): if v is None: parts.append(k.replace('_', '-')) else: parts.append(_formatparam(k.replace('_', '-'), v)) if _value is not None: parts.insert(0, _value) return '; '.join(parts) def add_header(self, _name, _value, **kwargs): """Extended header setting. name is the header field to add. keyword arguments can be used to set additional parameters for the header field, with underscores converted to dashes. Normally the parameter will be added as key="value" unless value is None, in which case only the key will be added. If a parameter value contains non-ASCII characters it can be specified as a three-tuple of (charset, language, value), in which case it will be encoded according to RFC2231 rules. Otherwise it will be encoded using the utf-8 charset and a language of ''. Examples: msg.add_header('content-disposition', 'attachment', filename='bud.gif') msg.add_header('content-disposition', 'attachment', filename=('utf-8', '', Fußballer.ppt')) msg.add_header('content-disposition', 'attachment', filename='Fußballer.ppt')) """ self.resp_headers[_name] = self.gen_header(_value, **kwargs) mime_type = property( lambda self: self.get_mime_type(), lambda self, what: self.set_mime_type(what), ) """Provides an easy way to set outgoing mime type. Synonym for `content_type`""" content_type = mime_type """Provides an easy way to set outgoing mime type. Synonym for `mime_type`""" class HttpMethodContext(MethodContext): """The Http-Specific method context. Http-Specific information is stored in the transport attribute using the :class:`HttpTransportContext` class. """ # because ctor signatures differ between TransportContext and # HttpTransportContext, we needed a new variable TransportContext = None HttpTransportContext = HttpTransportContext def __init__(self, transport, req_env, content_type): super(HttpMethodContext, self).__init__(transport, MethodContext.SERVER) self.transport = self.HttpTransportContext(self, transport, req_env, content_type) """Holds the HTTP-specific information""" def set_out_protocol(self, what): self._out_protocol = what if self._out_protocol.app is None: self._out_protocol.set_app(self.app) if isinstance(self.transport, HttpTransportContext): self.transport.set_mime_type(what.mime_type) out_protocol = property(MethodContext.get_out_protocol, set_out_protocol) """Assigning an out protocol overrides the mime type of the transport.""" class HttpBase(ServerBase): transport = 'http://schemas.xmlsoap.org/soap/http' SLASH = '/' SLASHPER = '/%s' def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(HttpBase, self).__init__(app) self.chunked = chunked self.max_content_length = max_content_length self.block_length = block_length self._http_patterns = set() for k, v in self.app.interface.service_method_map.items(): # p_ stands for primary, ie the non-aux method p_method_descriptor = v[0] for patt in p_method_descriptor.patterns: if isinstance(patt, HttpPattern): self._http_patterns.add(patt) # this makes sure similar addresses with patterns are evaluated after # addresses with wildcards, which puts the more specific addresses to # the front. self._http_patterns = list(reversed(sorted(self._http_patterns, key=lambda x: (x.address, x.host) ))) @classmethod def get_patt_verb(cls, patt): return patt.verb_re @classmethod def get_patt_host(cls, patt): return patt.host_re @classmethod def get_patt_address(cls, patt): return patt.address_re def match_pattern(self, ctx, method='', path='', host=''): """Sets ctx.method_request_string if there's a match. It's O(n) which means you should keep your number of patterns as low as possible. :param ctx: A MethodContext instance :param method: The verb in the HTTP Request (GET, POST, etc.) :param host: The contents of the ``Host:`` header :param path: Path but not the arguments. (i.e. stuff before '?', if it's there) """ if not path.startswith(self.SLASH): path = self.SLASHPER % (path,) params = defaultdict(list) for patt in self._http_patterns: assert isinstance(patt, HttpPattern) if patt.verb is not None: match = self.get_patt_verb(patt).match(method) if match is None: continue if not (match.span() == (0, len(method))): continue for k,v in match.groupdict().items(): params[k].append(v) if patt.host is not None: match = self.get_patt_host(patt).match(host) if match is None: continue if not (match.span() == (0, len(host))): continue for k, v in match.groupdict().items(): params[k].append(v) if patt.address is None: if path.split(self.SLASH)[-1] != patt.endpoint.name: continue else: match = self.get_patt_address(patt).match(path) if match is None: continue if not (match.span() == (0, len(path))): continue for k,v in match.groupdict().items(): params[k].append(v) d = patt.endpoint assert isinstance(d, MethodDescriptor) ctx.method_request_string = d.name break return params @property def has_patterns(self): return len(self._http_patterns) > 0 spyne-spyne-2.14.0/spyne/server/msgpack.py000066400000000000000000000154171417664205300205530ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import msgpack from mmap import mmap from collections import OrderedDict from spyne import MethodContext, TransportContext, Address from spyne.auxproc import process_contexts from spyne.error import ValidationError, InternalError from spyne.server import ServerBase from spyne.util.six import binary_type try: from twisted.internet.defer import Deferred except ImportError as e: def Deferred(*_, **__): raise e MSGPACK_SHELL_OVERHEAD = 10 def _process_v1_msg(prot, msg): header = None body = msg[1] if not isinstance(body, (binary_type, mmap, memoryview)): raise ValidationError(body, "Body must be a bytestream.") if len(msg) > 2: header = msg[2] if not isinstance(header, dict): raise ValidationError(header, "Header must be a dict.") for k, v in header.items(): header[k] = msgpack.unpackb(v) ctx = MessagePackMethodContext(prot, MessagePackMethodContext.SERVER) ctx.in_string = [body] ctx.transport.in_header = header return ctx class MessagePackTransportContext(TransportContext): def __init__(self, parent, transport): super(MessagePackTransportContext, self).__init__(parent, transport) self.in_header = None self.protocol = None self.inreq_queue = OrderedDict() self.request_len = None def get_peer(self): if self.protocol is not None: peer = self.protocol.transport.getPeer() return Address.from_twisted_address(peer) class MessagePackOobMethodContext(object): __slots__ = 'd' def __init__(self): if Deferred is not None: self.d = Deferred() else: self.d = None def close(self): if self.d is not None and not self.d.called: self.d.cancel() class MessagePackMethodContext(MethodContext): TransportContext = MessagePackTransportContext def __init__(self, transport, way): self.oob_ctx = None super(MessagePackMethodContext, self).__init__(transport, way) def close(self): super(MessagePackMethodContext, self).close() if self.transport is not None: self.transport.protocol = None self.transport = None if self.oob_ctx is not None: self.oob_ctx.close() class MessagePackTransportBase(ServerBase): # These are all placeholders that need to be overridden in subclasses OUT_RESPONSE_NO_ERROR = None OUT_RESPONSE_CLIENT_ERROR = None OUT_RESPONSE_SERVER_ERROR = None IN_REQUEST = None def __init__(self, app): super(MessagePackTransportBase, self).__init__(app) self._version_map = { self.IN_REQUEST: _process_v1_msg } def produce_contexts(self, msg): """Produce contexts based on incoming message. :param msg: Parsed request in this format: `[IN_REQUEST, body, header]` """ if not isinstance(msg, (list, tuple)): logger.debug("Incoming request: %r", msg) raise ValidationError(msg, "Request must be a list") if not len(msg) >= 2: logger.debug("Incoming request: %r", msg) raise ValidationError(len(msg), "Request must have at least two " "elements. It has %r") if not isinstance(msg[0], int): logger.debug("Incoming request: %r", msg) raise ValidationError(msg[0], "Request version must be an integer. " "It was %r") processor = self._version_map.get(msg[0], None) if processor is None: logger.debug("Invalid incoming request: %r", msg) raise ValidationError(msg[0], "Unknown request type %r") msglen = len(msg[1]) # shellen = len(msgpack.packb(msg)) # logger.debug("Shell size: %d, message size: %d, diff: %d", # shellen, msglen, shellen - msglen) # some approx. msgpack overhead based on observations of what's above. msglen += MSGPACK_SHELL_OVERHEAD initial_ctx = processor(self, msg) contexts = self.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] p_ctx.transport.request_len = msglen return p_ctx, others def process_contexts(self, contexts): p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error) try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) contexts.out_error = InternalError("Serialization Error.") return self.handle_error(contexts, others, contexts.out_error) def handle_error(self, p_ctx, others, error): self.get_out_string(p_ctx) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) def handle_transport_error(self, error): return msgpack.dumps(str(error)) def pack(self, ctx): ctx.out_string = msgpack.packb([self.OUT_RESPONSE_NO_ERROR, b''.join(ctx.out_string)]), class MessagePackServerBase(MessagePackTransportBase): """Contains the transport protocol logic but not the transport itself. Subclasses should implement logic to move bitstreams in and out of this class.""" OUT_RESPONSE_NO_ERROR = 0 OUT_RESPONSE_CLIENT_ERROR = 1 OUT_RESPONSE_SERVER_ERROR = 2 IN_REQUEST = 1 spyne-spyne-2.14.0/spyne/server/null.py000066400000000000000000000164141417664205300200760ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.null`` module contains the NullServer class and its helper objects. The name comes from the "null modem connection". Look it up. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from spyne import MethodContext, BODY_STYLE_BARE, ComplexModelBase, \ BODY_STYLE_EMPTY, Ignored from spyne.client import Factory from spyne.const.ansi_color import LIGHT_RED from spyne.const.ansi_color import LIGHT_BLUE from spyne.const.ansi_color import END_COLOR from spyne.server import ServerBase _big_header = ('=' * 40) + LIGHT_RED _big_footer = END_COLOR + ('=' * 40) _small_header = ('-' * 20) + LIGHT_BLUE _small_footer = END_COLOR + ('-' * 20) class NullServer(ServerBase): """A server that doesn't support any transport at all -- it's implemented to test services without having to run a server. Note that: 1) ``**kwargs`` overwrite ``*args``. 2) You can do: :: logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) to hide context delimiters in logs. """ transport = 'noconn://null.spyne' MethodContext = MethodContext def __init__(self, app, ostr=False, locale='C', appinit=True): self.do_appinit = appinit super(NullServer, self).__init__(app) self.service = _FunctionProxy(self, self.app, is_async=False) self.is_async = _FunctionProxy(self, self.app, is_async=True) self.factory = Factory(self.app) self.ostr = ostr self.locale = locale self.url = "http://spyne.io/null" def appinit(self): if self.do_appinit: super(NullServer, self).appinit() def get_wsdl(self): return self.app.get_interface_document(self.url) def set_options(self, **kwargs): self.service.in_header = kwargs.get('soapheaders', self.service.in_header) def get_services(self): return self.app.interface.service_method_map class _FunctionProxy(object): def __init__(self, server, app, is_async): self._app = app self._server = server self.in_header = None self.is_async = is_async def __getattr__(self, key): return _FunctionCall(self._app, self._server, key, self.in_header, self._server.ostr, self._server.locale, self.is_async) def __getitem__(self, key): return self.__getattr__(key) class _FunctionCall(object): def __init__(self, app, server, key, in_header, ostr, locale, async_): self.app = app self._key = key self._server = server self._in_header = in_header self._ostr = ostr self._locale = locale self._async = async_ def __call__(self, *args, **kwargs): initial_ctx = self._server.MethodContext(self, MethodContext.SERVER) initial_ctx.method_request_string = self._key initial_ctx.in_header = self._in_header initial_ctx.transport.type = NullServer.transport initial_ctx.locale = self._locale contexts = self.app.in_protocol.generate_method_contexts(initial_ctx) retval = None logger.warning("%s start request %s" % (_big_header, _big_footer)) if self._async: from twisted.internet.defer import Deferred for cnt, ctx in enumerate(contexts): # this reconstruction is quite costly. I wonder whether it's a # problem though. _type_info = ctx.descriptor.in_message._type_info ctx.in_object = [None] * len(_type_info) for i in range(len(args)): ctx.in_object[i] = args[i] for i, k in enumerate(_type_info.keys()): val = kwargs.get(k, None) if val is not None: ctx.in_object[i] = val if ctx.descriptor.body_style == BODY_STYLE_BARE: ctx.in_object = ctx.descriptor.in_message \ .get_serialization_instance(ctx.in_object) if cnt == 0: p_ctx = ctx else: ctx.descriptor.aux.initialize_context(ctx, p_ctx, error=None) # do # logging.getLogger('spyne.server.null').setLevel(logging.CRITICAL) # to hide the following logger.warning("%s start context %s" % (_small_header, _small_footer)) logger.info("%r.%r" % (ctx.service_class, ctx.descriptor.function)) try: self.app.process_request(ctx) finally: logger.warning("%s end context %s" % (_small_header, _small_footer)) if cnt == 0: if self._async and isinstance(ctx.out_object[0], Deferred): retval = ctx.out_object[0] retval.addCallback(_cb_async, ctx, cnt, self) else: retval = _cb_sync(ctx, cnt, self) if not self._async: p_ctx.close() logger.warning("%s end request %s" % (_big_header, _big_footer)) return retval def _cb_async(ret, ctx, cnt, fc): if issubclass(ctx.descriptor.out_message, ComplexModelBase): if len(ctx.descriptor.out_message._type_info) == 0: ctx.out_object = [None] elif len(ctx.descriptor.out_message._type_info) == 1: ctx.out_object = [ret] else: ctx.out_object = ret else: ctx.out_object = [ret] return _cb_sync(ctx, cnt, fc) def _cb_sync(ctx, cnt, fc): retval = None if ctx.out_error: raise ctx.out_error else: if isinstance(ctx.out_object, (list, tuple)) \ and len(ctx.out_object) > 0 \ and isinstance(ctx.out_object[0], Ignored): retval = ctx.out_object[0] elif ctx.descriptor.is_out_bare(): retval = ctx.out_object[0] elif ctx.descriptor.body_style is BODY_STYLE_EMPTY: retval = None elif len(ctx.descriptor.out_message._type_info) == 0: retval = None elif len(ctx.descriptor.out_message._type_info) == 1: retval = ctx.out_object[0] else: retval = ctx.out_object if cnt == 0 and fc._ostr: fc._server.get_out_string(ctx) retval = ctx.out_string if cnt > 0: ctx.close() return retval spyne-spyne-2.14.0/spyne/server/pyramid.py000066400000000000000000000036531417664205300205720ustar00rootroot00000000000000# encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.pyramid`` module contains a Pyramid-compatible Http transport. It's a thin wrapper around :class:`spyne.server.wsgi.WsgiApplication`. """ from __future__ import absolute_import from pyramid.response import Response from spyne.server.wsgi import WsgiApplication class PyramidApplication(WsgiApplication): """Pyramid View Wrapper. Use this for regular RPC""" def __call__(self, request): retval = Response() def start_response(status, headers): status, reason = status.split(' ', 1) retval.status_int = int(status) for header, value in headers: retval.headers[header] = value response = WsgiApplication.__call__(self, request.environ, start_response) retval.body = b"".join(response) return retval def set_response(self, retval, response): retval.body = b"".join(response) class StreamingPyramidApplication(WsgiApplication): """You should use this when you're generating HUGE data as response.""" def set_response(self, retval, response): retval.app_iter = response spyne-spyne-2.14.0/spyne/server/twisted/000077500000000000000000000000001417664205300202275ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/server/twisted/__init__.py000066400000000000000000000017341417664205300223450ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # def log_and_let_go(err, logger): logger.error(err.getTraceback()) return err from spyne.server.twisted.http import TwistedWebResource from spyne.server.twisted.websocket import TwistedWebSocketResource spyne-spyne-2.14.0/spyne/server/twisted/_base.py000066400000000000000000000046361417664205300216630ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from twisted.internet.defer import Deferred from twisted.internet.interfaces import IPullProducer from twisted.web.iweb import UNKNOWN_LENGTH from zope.interface import implementer @implementer(IPullProducer) class Producer(object): deferred = None def __init__(self, body, consumer): """:param body: an iterable of strings""" # check to see if we can determine the length try: len(body) # iterator? self.length = sum([len(fragment) for fragment in body]) self.body = iter(body) except TypeError: self.length = UNKNOWN_LENGTH self.body = body self.deferred = Deferred() self.consumer = consumer def resumeProducing(self): try: chunk = next(self.body) except StopIteration as e: self.consumer.unregisterProducer() if self.deferred is not None: self.deferred.callback(self.consumer) self.deferred = None return self.consumer.write(chunk) def pauseProducing(self): pass def stopProducing(self): if self.deferred is not None: self.deferred.errback( Exception("Consumer asked us to stop producing")) self.deferred = None from spyne import Address _TYPE_MAP = {'TCP': Address.TCP4, 'TCP6': Address.TCP6, 'UDP': Address.UDP4, 'UDP6': Address.UDP6} def _address_from_twisted_address(peer): return Address( type=_TYPE_MAP.get(peer.type, None), host=peer.host, port=peer.port) Address.from_twisted_address = staticmethod(_address_from_twisted_address) spyne-spyne-2.14.0/spyne/server/twisted/http.py000066400000000000000000000656541417664205300216000ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.twisted`` module contains a server transport compatible with the Twisted event loop. It uses the TwistedWebResource object as transport. Also see the twisted examples in the examples directory of the source distribution. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: resource = TwistedWebResource(...) resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary. If you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in wsdl seriously, all requests will go to the designated url above which can make testing a bit difficult. Use in moderation. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import re import cgi import gzip import shutil import threading from os import fstat from mmap import mmap from inspect import isclass from collections import namedtuple from tempfile import TemporaryFile from twisted.web import static from twisted.web.server import NOT_DONE_YET, Request from twisted.web.resource import Resource, NoResource, ForbiddenResource from twisted.web.static import getTypeAndEncoding from twisted.python.log import err from twisted.python.failure import Failure from twisted.internet import reactor from twisted.internet.task import deferLater from twisted.internet.defer import Deferred from twisted.internet.threads import deferToThread from spyne import Redirect, Address from spyne.application import logger_server from spyne.application import get_fault_string_from_exception from spyne.util import six from spyne.error import InternalError, ValidationError from spyne.auxproc import process_contexts from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import END_COLOR from spyne.const.http import HTTP_404, HTTP_200 from spyne.model import PushBase, File, ComplexModelBase from spyne.model.fault import Fault from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase from spyne.server.http import HttpMethodContext from spyne.server.http import HttpTransportContext from spyne.server.twisted._base import Producer from spyne.server.twisted import log_and_let_go from spyne.util.address import address_parser from spyne.util.six import text_type, string_types from spyne.util.six.moves.urllib.parse import unquote if not six.PY2: from urllib.request import unquote_to_bytes def _render_file(file, request): """ Begin sending the contents of this L{File} (or a subset of the contents, based on the 'range' header) to the given request. """ file.restat(False) if file.type is None: file.type, file.encoding = getTypeAndEncoding(file.basename(), file.contentTypes, file.contentEncodings, file.defaultType) if not file.exists(): return file.childNotFound.render(request) if file.isdir(): return file.redirect(request) request.setHeader('accept-ranges', 'bytes') try: fileForReading = file.openForReading() except IOError as e: import errno if e[0] == errno.EACCES: return ForbiddenResource().render(request) else: raise #if request.setLastModified(file.getmtime()) is CACHED: # return '' producer = file.makeProducer(request, fileForReading) if request.method == 'HEAD': return '' producer.start() # and make sure the connection doesn't get closed return NOT_DONE_YET def _set_response_headers(request, headers): retval = [] for k, v in headers.items(): if isinstance(v, (list, tuple)): request.responseHeaders.setRawHeaders(k, v) else: request.responseHeaders.setRawHeaders(k, [v]) return retval def _reconstruct_url(request): # HTTP "Hosts" header only supports ascii server_name = request.getHeader(b"x-forwarded-host") server_port = request.getHeader(b"x-forwarded-port") if server_port is not None: try: server_port = int(server_port) except Exception as e: logger.debug("Ignoring exception: %r for value %r", e, server_port) server_port = None is_secure = request.getHeader(b"x-forwarded-proto") if is_secure is not None: is_secure = is_secure == 'https' if server_name is None: server_name = request.getRequestHostname().decode('ascii') if server_port is None: server_port = request.getHost().port if is_secure is None: is_secure = bool(request.isSecure()) if (is_secure, server_port) not in ((True, 443), (False, 80)): server_name = '%s:%d' % (server_name, server_port) if is_secure: url_scheme = 'https' else: url_scheme = 'http' uri = _decode_path(request.uri) return ''.join([url_scheme, "://", server_name, uri]) class _Transformer(object): def __init__(self, req): self.req = req def get(self, key, default): key = key.lower() if six.PY2: if key.startswith((b'http_', b'http-')): key = key[5:] else: if isinstance(key, bytes): if key.startswith((b'http_', b'http-')): key = key[5:] else: if key.startswith(('http_', 'http-')): key = key[5:] retval = self.req.getHeader(key) if retval is None: retval = default return retval class TwistedHttpTransportContext(HttpTransportContext): def set_mime_type(self, what): if isinstance(what, text_type): what = what.encode('ascii', errors='replace') super(TwistedHttpTransportContext, self).set_mime_type(what) self.req.setHeader('Content-Type', what) def get_cookie(self, key): return self.req.getCookie(key) def get_path(self): return self.req.URLPath().path def get_path_and_qs(self): return self.req.uri def get_request_method(self): return self.req.method def get_request_content_type(self): return self.req.getHeader("Content-Type") def get_peer(self): peer = Address.from_twisted_address(self.req.transport.getPeer()) addr = address_parser.get_ip(_Transformer(self.req)) if addr is None: return peer if address_parser.is_valid_ipv4(addr): return Address(type=Address.TCP4, host=addr, port=0) if address_parser.is_valid_ipv6(addr): return Address(type=Address.TCP6, host=addr, port=0) class TwistedHttpMethodContext(HttpMethodContext): HttpTransportContext = TwistedHttpTransportContext def _decode_path(fragment): if six.PY2: return unquote(fragment) return unquote_to_bytes(fragment) class TwistedHttpTransport(HttpBase): SLASH = b'/' SLASHPER = b'/%s' KEY_ENCODING = 'utf8' @classmethod def get_patt_verb(cls, patt): return patt.verb_b_re @classmethod def get_patt_host(cls, patt): return patt.host_b_re @classmethod def get_patt_address(cls, patt): return patt.address_b_re def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(TwistedHttpTransport, self).__init__(app, chunked=chunked, max_content_length=max_content_length, block_length=block_length) self.reactor_thread = None def _cb(): self.reactor_thread = threading.current_thread() deferLater(reactor, 0, _cb) def pusher_init(self, p_ctx, gen, _cb_push_finish, pusher, interim): if pusher.orig_thread != self.reactor_thread: return deferToThread(super(TwistedHttpTransport, self).pusher_init, p_ctx, gen, _cb_push_finish, pusher, interim) return super(TwistedHttpTransport, self).pusher_init( p_ctx, gen, _cb_push_finish, pusher, interim) @staticmethod def set_out_document_push(ctx): class _ISwearImAGenerator(object): def send(self, data): if not data: return ctx.out_stream.write(data) ctx.out_document = _ISwearImAGenerator() def pusher_try_close(self, ctx, pusher, retval): # the whole point of this function is to call ctx.out_stream.finish() # when a *root* pusher has no more data to send. interim pushers don't # have to close anything. if isinstance(retval, Deferred): def _eb_push_close(f): assert isinstance(f, Failure) logger.error(f.getTraceback()) subretval = super(TwistedHttpTransport, self) \ .pusher_try_close(ctx, pusher, retval) if not pusher.interim: ctx.out_stream.finish() return subretval def _cb_push_close(r): def _eb_inner(f): if not pusher.interim: ctx.out_stream.finish() return f if not isinstance(r, Deferred): retval = super(TwistedHttpTransport, self) \ .pusher_try_close(ctx, pusher, r) if not pusher.interim: ctx.out_stream.finish() return retval return r \ .addCallback(_cb_push_close) \ .addErrback(_eb_inner) \ .addErrback(log_and_let_go, logger) return retval \ .addCallback(_cb_push_close) \ .addErrback(_eb_push_close) \ .addErrback(log_and_let_go, logger) super(TwistedHttpTransport, self).pusher_try_close(ctx, pusher, retval) if not pusher.interim: retval = ctx.out_stream.finish() return retval def _decode_dict_py2(self, d): retval = {} for k, v in d.items(): l = [] for v2 in v: if isinstance(v2, string_types): l.append(unquote(v2)) else: l.append(v2) retval[k] = l return retval def _decode_dict(self, d): retval = {} for k, v in d.items(): l = [] for v2 in v: if isinstance(v2, str): l.append(unquote(v2)) elif isinstance(v2, bytes): l.append(unquote(v2.decode(self.KEY_ENCODING))) else: l.append(v2) if isinstance(k, str): retval[k] = l elif isinstance(k, bytes): retval[k.decode(self.KEY_ENCODING)] = l else: raise ValidationError(k) return retval def decompose_incoming_envelope(self, prot, ctx, message): """This function is only called by the HttpRpc protocol to have the twisted web's Request object is parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. """ request = ctx.in_document assert isinstance(request, Request) ctx.in_header_doc = dict(request.requestHeaders.getAllRawHeaders()) ctx.in_body_doc = request.args for fi in ctx.transport.file_info: assert isinstance(fi, _FileInfo) if fi.file_name is None: continue l = ctx.in_body_doc.get(fi.field_name, None) if l is None: l = ctx.in_body_doc[fi.field_name] = [] l.append( File.Value(name=fi.file_name, type=fi.file_type, data=fi.data) ) # this is a huge hack because twisted seems to take the slashes in urls # too seriously. postpath = getattr(request, 'realpostpath', None) if postpath is None: postpath = request.path if postpath is not None: postpath = _decode_path(postpath) params = self.match_pattern(ctx, request.method, postpath, request.getHeader(b'Host')) if ctx.method_request_string is None: # no pattern match ctx.method_request_string = u'{%s}%s' % ( self.app.interface.get_tns(), _decode_path(request.path.rsplit(b'/', 1)[-1]).decode("utf8"), ) logger.debug(u"%sMethod name: %r%s" % (LIGHT_GREEN, ctx.method_request_string, END_COLOR)) for k, v in params.items(): val = ctx.in_body_doc.get(k, []) val.extend(v) ctx.in_body_doc[k] = val r = {} if six.PY2: ctx.in_header_doc = self._decode_dict_py2(ctx.in_header_doc) ctx.in_body_doc = self._decode_dict_py2(ctx.in_body_doc) else: ctx.in_header_doc = self._decode_dict(ctx.in_header_doc) ctx.in_body_doc = self._decode_dict(ctx.in_body_doc) # This is consistent with what server.wsgi does. if request.method in ('POST', 'PUT', 'PATCH'): for k, v in ctx.in_body_doc.items(): if v == ['']: ctx.in_body_doc[k] = [None] logger.debug("%r", ctx.in_body_doc) FIELD_NAME_RE = re.compile(r'name="([^"]+)"') FILE_NAME_RE = re.compile(r'filename="([^"]+)"') _FileInfo = namedtuple("_FileInfo", "field_name file_name file_type data") def _get_file_info(ctx): """We need this hack because twisted doesn't offer a way to get file name from Content-Disposition header. """ retval = [] request = ctx.transport.req headers = request.getAllHeaders() content_type = headers.get('content-type', None) if content_type is None: return retval content = request.content content_encoding = headers.get('content-encoding', None) if content_encoding == b'gzip': request.content.seek(0) content = TemporaryFile() with gzip.GzipFile(fileobj=request.content) as ifstr: shutil.copyfileobj(ifstr, content) content.seek(0) img = cgi.FieldStorage( fp=content, headers=ctx.in_header_doc, environ={ 'REQUEST_METHOD': request.method, 'CONTENT_TYPE': content_type, } ) try: keys = img.keys() except TypeError: return retval for k in keys: fields = img[k] if isinstance(fields, cgi.FieldStorage): fields = (fields,) for field in fields: file_type = field.type file_name = field.disposition_options.get('filename', None) if file_name is not None: retval.append(_FileInfo(k, file_name, file_type, [mmap(field.file.fileno(), 0)])) return retval def _has_fd(istr): if not hasattr(istr, 'fileno'): return False try: istr.fileno() except IOError: return False else: return True def get_twisted_child_with_default(res, path, request): # this hack is necessary because twisted takes the slash character in # http requests too seriously. i.e. it insists that a leaf node can only # handle the last path fragment. if res.prepath is None: request.realprepath = b'/' + b'/'.join(request.prepath) else: if not res.prepath.startswith(b'/'): request.realprepath = b'/' + res.prepath else: request.realprepath = res.prepath if path in res.children: retval = res.children[path] else: retval = res.getChild(path, request) if isinstance(retval, NoResource): retval = res else: request.realpostpath = request.path[ len(path) + (0 if path.startswith(b'/') else 1):] return retval class TwistedWebResource(Resource): """A server transport that exposes the application as a twisted web Resource. """ def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024, prepath=None): Resource.__init__(self) self.app = app self.http_transport = TwistedHttpTransport(app, chunked, max_content_length, block_length) self._wsdl = None self.prepath = prepath def getChildWithDefault(self, path, request): return get_twisted_child_with_default(self, path, request) def render(self, request): if request.method == b'GET' and ( request.uri.endswith(b'.wsdl') or request.uri.endswith(b'?wsdl')): return self.__handle_wsdl_request(request) return self.handle_rpc(request) def handle_rpc_error(self, p_ctx, others, error, request): logger.error(error) resp_code = p_ctx.transport.resp_code # If user code set its own response code, don't touch it. if resp_code is None: resp_code = p_ctx.out_protocol.fault_to_http_response_code(error) request.setResponseCode(int(resp_code[:3])) _set_response_headers(request, p_ctx.transport.resp_headers) # In case user code set its own out_* attributes before failing. p_ctx.out_document = None p_ctx.out_string = None p_ctx.out_object = error self.http_transport.get_out_string(p_ctx) retval = b''.join(p_ctx.out_string) p_ctx.close() process_contexts(self.http_transport, others, p_ctx, error=error) return retval def handle_rpc(self, request): initial_ctx = TwistedHttpMethodContext(self.http_transport, request, self.http_transport.app.out_protocol.mime_type) if _has_fd(request.content): f = request.content # it's best to avoid empty mappings. if fstat(f.fileno()).st_size == 0: initial_ctx.in_string = [''] else: initial_ctx.in_string = [mmap(f.fileno(), 0)] else: request.content.seek(0) initial_ctx.in_string = [request.content.read()] initial_ctx.transport.file_info = _get_file_info(initial_ctx) contexts = self.http_transport.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] p_ctx.active = True p_ctx.out_stream = request # TODO: Rate limiting p_ctx.active = True if p_ctx.in_error: return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, request) else: self.http_transport.get_in_object(p_ctx) if p_ctx.in_error: return self.handle_rpc_error(p_ctx, others, p_ctx.in_error, request) self.http_transport.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) ret = p_ctx.out_object[0] retval = NOT_DONE_YET if isinstance(ret, Deferred): ret.addCallback(_cb_deferred, request, p_ctx, others, resource=self) ret.addErrback(_eb_deferred, request, p_ctx, others, resource=self) ret.addErrback(log_and_let_go, logger) elif isinstance(ret, PushBase): self.http_transport.init_root_push(ret, p_ctx, others) else: try: retval = _cb_deferred(p_ctx.out_object, request, p_ctx, others, self, cb=False) except Exception as e: logger_server.exception(e) try: _eb_deferred(Failure(), request, p_ctx, others, resource=self) except Exception as e: logger_server.exception(e) return retval def __handle_wsdl_request(self, request): # disabled for performance reasons. # logger.debug("WSDL request headers: %r", # list(request.requestHeaders.getAllRawHeaders())) ctx = TwistedHttpMethodContext(self.http_transport, request, "text/xml; charset=utf-8") url = _reconstruct_url(request) if self.http_transport.doc.wsdl11 is None: return HTTP_404 if self._wsdl is None: self._wsdl = self.http_transport.doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl _set_response_headers(request, ctx.transport.resp_headers) try: if self._wsdl is None: self.http_transport.doc.wsdl11.build_interface_document(url) ctx.transport.wsdl = self._wsdl = \ self.http_transport.doc.wsdl11.get_interface_document() assert ctx.transport.wsdl is not None self.http_transport.event_manager.fire_event('wsdl', ctx) return ctx.transport.wsdl except Exception as e: ctx.transport.wsdl_error = e self.http_transport.event_manager.fire_event('wsdl_exception', ctx) raise finally: ctx.close() def _cb_request_finished(retval, request, p_ctx): request.finish() p_ctx.close() def _eb_request_finished(retval, request, p_ctx): err(request) p_ctx.close() request.finish() def _cb_deferred(ret, request, p_ctx, others, resource, cb=True): ### set response headers resp_code = p_ctx.transport.resp_code # If user code set its own response code, don't touch it. if resp_code is None: resp_code = HTTP_200 request.setResponseCode(int(resp_code[:3])) _set_response_headers(request, p_ctx.transport.resp_headers) ### normalize response data om = p_ctx.descriptor.out_message single_class = None if cb: if p_ctx.descriptor.is_out_bare(): p_ctx.out_object = [ret] elif (not issubclass(om, ComplexModelBase)) or len(om._type_info) <= 1: p_ctx.out_object = [ret] if len(om._type_info) == 1: single_class, = om._type_info.values() else: p_ctx.out_object = ret else: p_ctx.out_object = ret ### start response retval = NOT_DONE_YET if isinstance(ret, PushBase): resource.http_transport.init_root_push(ret, p_ctx, others) elif ((isclass(om) and issubclass(om, File)) or (isclass(single_class) and issubclass(single_class, File))) and \ isinstance(p_ctx.out_protocol, HttpRpc) and \ getattr(ret, 'abspath', None) is not None: file = static.File(ret.abspath, defaultType=str(ret.type) or 'application/octet-stream') retval = _render_file(file, request) if retval != NOT_DONE_YET and cb: request.write(retval) request.finish() p_ctx.close() else: def _close_only_context(ret): p_ctx.close() request.notifyFinish() \ .addCallback(_close_only_context) \ .addErrback(_eb_request_finished, request, p_ctx) \ .addErrback(log_and_let_go, logger) else: ret = resource.http_transport.get_out_string(p_ctx) if not isinstance(ret, Deferred): producer = Producer(p_ctx.out_string, request) producer.deferred \ .addCallback(_cb_request_finished, request, p_ctx) \ .addErrback(_eb_request_finished, request, p_ctx) \ .addErrback(log_and_let_go, logger) try: request.registerProducer(producer, False) except Exception as e: logger_server.exception(e) try: _eb_deferred(Failure(), request, p_ctx, others, resource) except Exception as e: logger_server.exception(e) raise else: def _cb(ret): if isinstance(ret, Deferred): return ret \ .addCallback(_cb) \ .addErrback(_eb_request_finished, request, p_ctx) \ .addErrback(log_and_let_go, logger) else: return _cb_request_finished(ret, request, p_ctx) ret \ .addCallback(_cb) \ .addErrback(_eb_request_finished, request, p_ctx) \ .addErrback(log_and_let_go, logger) process_contexts(resource.http_transport, others, p_ctx) return retval def _eb_deferred(ret, request, p_ctx, others, resource): # DRY this with what's in Application.process_request if ret.check(Redirect): try: ret.value.do_redirect() # Now that the processing is switched to the outgoing message, # point ctx.protocol to ctx.out_protocol p_ctx.protocol = p_ctx.outprot_ctx _cb_deferred(None, request, p_ctx, others, resource, cb=False) p_ctx.fire_event('method_redirect') except Exception as e: logger_server.exception(e) p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) p_ctx.fire_event('method_redirect_exception') elif ret.check(Fault): p_ctx.out_error = ret.value ret = resource.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) p_ctx.fire_event('method_exception_object') request.write(ret) else: p_ctx.out_error = InternalError(ret.value) logger.error(ret.getTraceback()) ret = resource.handle_rpc_error(p_ctx, others, p_ctx.out_error, request) p_ctx.fire_event('method_exception_object') request.write(ret) request.finish() spyne-spyne-2.14.0/spyne/server/twisted/msgpack.py000066400000000000000000000411511417664205300222300ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import io import msgpack from time import time from hashlib import md5 from collections import deque, OrderedDict from itertools import chain from twisted.internet import reactor from twisted.internet.task import deferLater from twisted.internet.defer import Deferred, CancelledError from twisted.internet.protocol import Protocol, Factory, connectionDone, \ ClientFactory from twisted.python.failure import Failure from spyne import EventManager, Address, ServerBase, Fault from spyne.auxproc import process_contexts from spyne.error import InternalError from spyne.server.twisted import log_and_let_go class TwistedMessagePackProtocolFactory(Factory): IDLE_TIMEOUT_SEC = None def __init__(self, tpt): assert isinstance(tpt, ServerBase) self.tpt = tpt self.event_manager = EventManager(self) def buildProtocol(self, address): retval = TwistedMessagePackProtocol(self.tpt, factory=self) if self.IDLE_TIMEOUT_SEC is not None: retval.IDLE_TIMEOUT_SEC = self.IDLE_TIMEOUT_SEC return retval TwistedMessagePackProtocolServerFactory = TwistedMessagePackProtocolFactory class TwistedMessagePackProtocolClientFactory(ClientFactory): def __init__(self, tpt, max_buffer_size=2 * 1024 * 1024): assert isinstance(tpt, ServerBase), \ "%r is not a ServerBase instance" % tpt self.tpt = tpt self.max_buffer_size = max_buffer_size self.event_manager = EventManager(self) def buildProtocol(self, address): return TwistedMessagePackProtocol(self.tpt, max_buffer_size=self.max_buffer_size, factory=self) def _cha(*args): return args class TwistedMessagePackProtocol(Protocol): IDLE_TIMEOUT_SEC = 0 IDLE_TIMEOUT_MSG = 'idle timeout' MAX_INACTIVE_CONTEXTS = float('inf') def __init__(self, tpt, max_buffer_size=2 * 1024 * 1024, out_chunk_size=0, out_chunk_delay_sec=1, max_in_queue_size=0, factory=None): """Twisted protocol implementation for Spyne's MessagePack transport. :param tpt: Spyne transport. It's an app-wide instance. :param max_buffer_size: Max. encoded message size. :param out_chunk_size: Split :param factory: Twisted protocol factory Supported events: * ``outresp_flushed(ctx, ctxid, data)`` Called right after response data is flushed to the socket. * ctx: Always None * ctxid: Integer equal to ``id(ctx)`` * data: Flushed bytes object """ from spyne.server.msgpack import MessagePackTransportBase assert isinstance(tpt, MessagePackTransportBase), \ "Expected {!r} got {!r}".format(MessagePackTransportBase, type(tpt)) self.spyne_tpt = tpt self._buffer = msgpack.Unpacker(raw=True, max_buffer_size=max_buffer_size) self.out_chunk_size = out_chunk_size self.out_chunk_delay_sec = out_chunk_delay_sec self.max_in_queue_size = max_in_queue_size self.factory = factory self.sessid = '' self._delaying = None self.sent_bytes = 0 self.recv_bytes = 0 self.idle_timer = None self.out_chunks = deque() self.inreq_queue = OrderedDict() self.inactive_queue = deque() self.disconnecting = False # FIXME: should we use this to raise an # invalid connection state exception ? @staticmethod def gen_chunks(l, n): """Yield successive n-sized chunks from l.""" if isinstance(l, io.BufferedIOBase): while True: data = l.read(n) if not data: break yield data l.close() else: for i in range(0, len(l), n): yield l[i:i+n] def gen_sessid(self, *args): """It's up to you to use this in a subclass.""" retval = _cha( Address.from_twisted_address(self.transport.getPeer()), time(), *args ) return md5(repr(retval).encode('utf8')).hexdigest() def connectionMade(self): logger.debug("%08x connection made", id(self)) self.sessid = '' self._delaying = None self.sent_bytes = 0 self.recv_bytes = 0 self.idle_timer = None self.out_chunks = deque() self.inreq_queue = OrderedDict() self.inactive_queue = deque() self.active_queue = dict() self.disconnecting = False # FIXME: should we use this to raise an # invalid connection state exception ? self._reset_idle_timer() if self.factory is not None: self.factory.event_manager.fire_event("connection_made", self) def connectionLost(self, reason=connectionDone): if reason is connectionDone: logger.debug("%08x connection done", id(self)) else: logger.debug("%08x connection lost: %s", id(self), reason) self.disconnecting = False if self.factory is not None: self.factory.event_manager.fire_event("connection_lost", self) self._cancel_idle_timer() def _cancel_idle_timer(self): if self.idle_timer is not None: if not self.idle_timer.called: # FIXME: Workaround for a bug in Twisted 18.9.0 when # DelayedCall.debug == True try: self.idle_timer.cancel() except AttributeError: del self.idle_timer.func del self.idle_timer.args del self.idle_timer.kw self.idle_timer = None def dataReceived(self, data): self._buffer.feed(data) self.recv_bytes += len(data) self._reset_idle_timer() for msg in self._buffer: try: self.process_incoming_message(msg) except Exception as e: # If you get this error, you are in serious trouble # This needs to be fixed ASAP logger.error( "Error %r while processing incoming data %r", e, msg) raise if self.disconnecting: return def _reset_idle_timer(self): if self.idle_timer is not None: t = self.idle_timer self.idle_timer = None if not t.called: t.cancel() if self.IDLE_TIMEOUT_SEC is not None and self.IDLE_TIMEOUT_SEC > 0: self.idle_timer = deferLater(reactor, self.IDLE_TIMEOUT_SEC, self.loseConnection, self.IDLE_TIMEOUT_MSG) \ .addErrback(self._err_idle_cancelled) \ .addErrback(self._err_idle_cancelled_unknown_error) def _err_idle_cancelled(self, err): err.trap(CancelledError) # do nothing. def _err_idle_cancelled_unknown_error(self, err): logger.error("Sessid %s error cancelling idle timer: %s", self.sessid, err.getTraceback()) self.idle_timer = None def loseConnection(self, reason=None): self.disconnecting = True self.idle_timer = None logger.debug("Aborting connection because %s", reason) self.transport.abortConnection() def process_incoming_message(self, msg, oob=None): p_ctx, others = self.spyne_tpt.produce_contexts(msg) p_ctx.oob_ctx = oob p_ctx.transport.remote_addr = Address.from_twisted_address( self.transport.getPeer()) p_ctx.transport.protocol = self p_ctx.transport.sessid = self.sessid self.inactive_queue.append((p_ctx, others)) self.process_inactive() @property def num_active_contexts(self): return len(self.inreq_queue) @property def num_inactive_contexts(self): return len(self.inactive_queue) def process_inactive(self): peer = self.transport.getPeer() addr_str = Address.from_twisted_address(peer) if self.max_in_queue_size == 0: while self.num_inactive_contexts > 0: p_ctx, others = self.inactive_queue.popleft() self.active_queue[id(p_ctx)] = p_ctx self.inreq_queue[id(p_ctx)] = None self.process_contexts(p_ctx, others) else: while self.num_active_contexts < self.max_in_queue_size and \ self.num_inactive_contexts > 0: p_ctx, others = self.inactive_queue.popleft() self.active_queue[id(p_ctx)] = p_ctx self.inreq_queue[id(p_ctx)] = None self.process_contexts(p_ctx, others) if self.num_active_contexts > self.MAX_INACTIVE_CONTEXTS: logger.error("%s Too many inactive contexts. " "Closing connection.", addr_str) self.loseConnection("Too many inactive contexts") logger.debug("%s active %d inactive %d", addr_str, self.num_active_contexts, self.num_inactive_contexts) def enqueue_outresp_data(self, ctxid, data): assert self.inreq_queue[ctxid] is None self.inreq_queue[ctxid] = data for k, v in list(self.inreq_queue.items()): if v is None: break self.out_write(v) self.spyne_tpt.event_manager.fire_event('outresp_flushed', None, k, v) del self.inreq_queue[k] self.active_queue[k].close() del self.active_queue[k] self.process_inactive() def out_write(self, reqdata): if self.out_chunk_size == 0: if isinstance(reqdata, io.BufferedIOBase): nbytes = reqdata.tell() reqdata.seek(0) self.transport.write(reqdata.read()) else: nbytes = len(reqdata) self.transport.write(reqdata) self.sent_bytes += nbytes else: if isinstance(reqdata, io.BufferedIOBase): reqdata.seek(0) chunks = self.gen_chunks(reqdata, self.out_chunk_size) self.out_chunks.append(chunks) deferLater(reactor, 0, self._write_single_chunk) def _wait_for_next_chunk(self): return deferLater(reactor, self.out_chunk_delay_sec, self._write_single_chunk) def _write_single_chunk(self): try: chunk = next(chain.from_iterable(self.out_chunks)) except StopIteration: chunk = None self.out_chunks.clear() if chunk is None: self._delaying = None logger.debug("%s no more chunks...", self.sessid) else: self.transport.write(chunk) self.sent_bytes += len(chunk) if self.connected and not self.disconnecting: self._delaying = self._wait_for_next_chunk() logger.debug("%s One chunk of %d bytes written. Delaying " "before next chunk write...", self.sessid, len(chunk)) else: logger.debug("%s Disconnection detected, discarding " "remaining chunks", self.sessid) self.out_chunks.clear() def handle_error(self, p_ctx, others, exc): self.spyne_tpt.get_out_string(p_ctx) if isinstance(exc, InternalError): error = self.spyne_tpt.OUT_RESPONSE_SERVER_ERROR else: error = self.spyne_tpt.OUT_RESPONSE_CLIENT_ERROR data = p_ctx.out_document[0] if isinstance(data, dict): data = list(data.values()) # tag debug responses with the one from the relevant request tag = getattr(p_ctx.transport, 'tag', None) if tag is None: out_object = (error, msgpack.packb(data)) else: out_object = (error, msgpack.packb(data), tag) if p_ctx.oob_ctx is not None: p_ctx.oob_ctx.d.callback(out_object) return if p_ctx.transport is not None: out_string = msgpack.packb(out_object) p_ctx.transport.resp_length = len(out_string) self.enqueue_outresp_data(id(p_ctx), out_string) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.error("Exception ignored from aux method: %r", e) logger.exception(e) def _register_callbacks(self, d, p_ctx, others): return d \ .addCallback(self._cb_deferred, p_ctx, others) \ .addErrback(self._eb_deferred, p_ctx, others) \ .addErrback(log_and_let_go, logger) def process_contexts(self, p_ctx, others): if p_ctx.in_error: self.handle_error(p_ctx, others, p_ctx.in_error) return self.spyne_tpt.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) self.handle_error(p_ctx, others, p_ctx.in_error) return self.spyne_tpt.get_out_object(p_ctx) if p_ctx.out_error: self.handle_error(p_ctx, others, p_ctx.out_error) return ret = p_ctx.out_object if isinstance(ret, Deferred): self._register_callbacks(ret, p_ctx, others) else: ret = p_ctx.out_object[0] if isinstance(ret, Deferred): self._register_callbacks(ret, p_ctx, others) else: self._cb_deferred(p_ctx.out_object, p_ctx, others, nowrap=True) def _eb_deferred(self, fail, p_ctx, others): assert isinstance(fail, Failure) if isinstance(fail.value, Fault): p_ctx.out_error = fail.value else: p_ctx.out_error = InternalError(fail.value) if not getattr(fail, 'logged', False): logger.error(fail.getTraceback()) try: self.handle_error(p_ctx, others, p_ctx.out_error) except Exception as e: logger.exception(e) raise def _cb_deferred(self, ret, p_ctx, others, nowrap=False): # this means callback is not invoked directly instead of as part of a # deferred chain if not nowrap: # if there is one return value or the output is bare (which means # there can't be anything other than 1 return value case) use the # enclosing list. otherwise, the return value is a tuple anyway, so # leave it be. if p_ctx.descriptor.is_out_bare(): p_ctx.out_object = [ret] else: if len(p_ctx.descriptor.out_message._type_info) > 1: p_ctx.out_object = ret else: p_ctx.out_object = [ret] if p_ctx.oob_ctx is not None: assert isinstance(p_ctx.oob_ctx.d, Deferred) p_ctx.oob_ctx.d.callback(p_ctx.out_object) return try: self.spyne_tpt.get_out_string(p_ctx) self.spyne_tpt.pack(p_ctx) out_string = b''.join(p_ctx.out_string) p_ctx.transport.resp_length = len(out_string) self.enqueue_outresp_data(id(p_ctx), out_string) except Exception as e: logger.exception(e) logger.error("%r", p_ctx) self.handle_error(p_ctx, others, InternalError(e)) finally: p_ctx.close() process_contexts(self.spyne_tpt, others, p_ctx) spyne-spyne-2.14.0/spyne/server/twisted/websocket.py000066400000000000000000000175061417664205300226000ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.twisted`` module contains a server transport compatible with the Twisted event loop. It uses the TwistedWebResource object as transport. Also see the twisted examples in the examples directory of the source distribution. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: resource = TwistedWebResource(...) resource.http_transport.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary -- if you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in wsdl seriously, all requests will go to the designated url above which can make testing a bit difficult. Use in moderation. This module is EXPERIMENTAL. Your mileage may vary. Patches are welcome. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from twisted.internet.defer import Deferred from twisted.internet.protocol import Factory # FIXME: Switch to: # from twisted.web.websockets import WebSocketsProtocol # from twisted.web.websockets import WebSocketsResource # from twisted.web.websockets import CONTROLS from spyne.util._twisted_ws import WebSocketsProtocol from spyne.util._twisted_ws import WebSocketsResource from spyne.util._twisted_ws import CONTROLS from spyne import MethodContext, TransportContext, Address from spyne.auxproc import process_contexts from spyne.model import PushBase from spyne.model.complex import ComplexModel from spyne.model.fault import Fault from spyne.server import ServerBase class WebSocketTransportContext(TransportContext): def __init__(self, parent, transport, type, client_handle): TransportContext.__init__(self, parent, transport, type) self.client_handle = client_handle """TwistedWebSocketProtocol instance.""" self.parent = parent """Parent Context""" def get_peer(self): if self.client_handle is not None: peer = self.client_handle.transport.getPeer() return Address.from_twisted_address(peer) class WebSocketMethodContext(MethodContext): def __init__(self, transport, client_handle): MethodContext.__init__(self, transport, MethodContext.SERVER) self.transport = WebSocketTransportContext(self, transport, 'ws', client_handle) class TwistedWebSocketProtocol(WebSocketsProtocol): """A protocol that parses and generates messages in a WebSocket stream.""" def __init__(self, transport, bookkeep=False, _clients=None): self._spyne_transport = transport self._clients = _clients self.__app_id = id(self) if bookkeep: self.connectionMade = self._connectionMade self.connectionLost = self._connectionLost @property def app_id(self): return self.__app_id @app_id.setter def app_id(self, what): entry = self._clients.get(self.__app_id, None) if entry: del self._clients[self.__app_id] self._clients[what] = entry self.__app_id = what def _connectionMade(self): WebSocketsProtocol.connectionMade(self) self._clients[self.app_id] = self def _connectionLost(self, reason): del self._clients[id(self)] def frameReceived(self, opcode, data, fin): tpt = self._spyne_transport initial_ctx = WebSocketMethodContext(tpt, client_handle=self) initial_ctx.in_string = [data] contexts = tpt.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error else: tpt.get_in_object(p_ctx) if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error else: tpt.get_out_object(p_ctx) if p_ctx.out_error: p_ctx.out_object = p_ctx.out_error def _cb_deferred(retval, cb=True): if cb and len(p_ctx.descriptor.out_message._type_info) <= 1: p_ctx.out_object = [retval] else: p_ctx.out_object = retval tpt.get_out_string(p_ctx) self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) p_ctx.close() process_contexts(tpt, others, p_ctx) def _eb_deferred(err): p_ctx.out_error = err.value if not issubclass(err.type, Fault): logger.error(err.getTraceback()) tpt.get_out_string(p_ctx) self.sendFrame(opcode, ''.join(p_ctx.out_string), fin) p_ctx.close() ret = p_ctx.out_object if isinstance(ret, (list, tuple)): ret = ret[0] if isinstance(ret, Deferred): ret.addCallback(_cb_deferred) ret.addErrback(_eb_deferred) elif isinstance(ret, PushBase): raise NotImplementedError() else: _cb_deferred(p_ctx.out_object, cb=False) class TwistedWebSocketFactory(Factory): def __init__(self, app, bookkeep=False, _clients=None): self.app = app self.transport = ServerBase(app) self.bookkeep = bookkeep self._clients = _clients if _clients is None: self._clients = {} def buildProtocol(self, addr): return TwistedWebSocketProtocol(self.transport, self.bookkeep, self._clients) class _Fake(object): pass def _FakeWrap(cls): class _Ret(ComplexModel): _type_info = {"ugh ": cls} return _Ret class _FakeCtx(object): def __init__(self, obj, cls): self.out_object = obj self.out_error = None self.descriptor = _Fake() self.descriptor.out_message = cls class InvalidRequestError(Exception): pass class TwistedWebSocketResource(WebSocketsResource): def __init__(self, app, bookkeep=False, clients=None): self.app = app self.clients = clients if clients is None: self.clients = {} if bookkeep: self.propagate = self.do_propagate WebSocketsResource.__init__(self, TwistedWebSocketFactory(app, bookkeep, self.clients)) def propagate(self): raise InvalidRequestError("You must enable bookkeeping to have " "message propagation work.") def get_doc(self, obj, cls=None): if cls is None: cls = obj.__class__ op = self.app.out_protocol ctx = _FakeCtx(obj, cls) op.serialize(ctx, op.RESPONSE) op.create_out_string(ctx) return ''.join(ctx.out_string) def do_propagate(self, obj, cls=None): doc = self.get_doc(obj, cls) for c in self.clients.itervalues(): print('sending to', c) c.sendFrame(CONTROLS.TEXT, doc, True) spyne-spyne-2.14.0/spyne/server/wsgi.py000066400000000000000000000531361417664205300200770ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ A server that uses http as transport via wsgi. It doesn't contain any server logic. """ import logging logger = logging.getLogger(__name__) import cgi import threading from inspect import isgenerator from itertools import chain from spyne import Address, File, Fault from spyne.util.six.moves.http_cookies import SimpleCookie from spyne.util.six.moves.urllib.parse import unquote, quote from spyne.application import get_fault_string_from_exception from spyne.auxproc import process_contexts from spyne.error import RequestTooLongError from spyne.protocol.http import HttpRpc from spyne.server.http import HttpBase, HttpMethodContext, HttpTransportContext from spyne.util.odict import odict from spyne.util.address import address_parser from spyne.const.ansi_color import LIGHT_GREEN from spyne.const.ansi_color import END_COLOR from spyne.const.http import HTTP_200 from spyne.const.http import HTTP_404 from spyne.const.http import HTTP_500 try: from spyne.protocol.soap.mime import apply_mtom except ImportError as _import_error_1: _local_import_error_1 = _import_error_1 # python 3 workaround def apply_mtom(*args, **kwargs): raise _local_import_error_1 try: from werkzeug.formparser import parse_form_data except ImportError as _import_error_2: _local_import_error_2 = _import_error_2 # python 3 workaround def parse_form_data(*args, **kwargs): raise _local_import_error_2 def _reconstruct_url(environ, protocol=True, server_name=True, path=True, query_string=True): """Rebuilds the calling url from values found in the environment. This algorithm was found via PEP 333, the wsgi spec and contributed by Ian Bicking. """ url = '' if protocol: url = environ['wsgi.url_scheme'] + '://' if server_name: if environ.get('HTTP_HOST'): url += environ['HTTP_HOST'] else: url += environ['SERVER_NAME'] if environ['wsgi.url_scheme'] == 'https': if environ['SERVER_PORT'] != '443': url += ':' + environ['SERVER_PORT'] else: if environ['SERVER_PORT'] != '80': url += ':' + environ['SERVER_PORT'] if path: if (quote(environ.get('SCRIPT_NAME', '')) == '/' and quote(environ.get('PATH_INFO', ''))[0] == '/'): #skip this if it is only a slash pass elif quote(environ.get('SCRIPT_NAME', ''))[0:2] == '//': url += quote(environ.get('SCRIPT_NAME', ''))[1:] else: url += quote(environ.get('SCRIPT_NAME', '')) url += quote(environ.get('PATH_INFO', '')) if query_string: if environ.get('QUERY_STRING'): url += '?' + environ['QUERY_STRING'] return url def _parse_qs(qs): pairs = (s2 for s1 in qs.split('&') for s2 in s1.split(';')) retval = odict() for name_value in pairs: if name_value is None or len(name_value) == 0: continue nv = name_value.split('=', 1) if len(nv) != 2: # Handle case of a control-name with no equal sign nv.append(None) name = unquote(nv[0].replace('+', ' ')) value = None if nv[1] is not None: value = unquote(nv[1].replace('+', ' ')) l = retval.get(name, None) if l is None: l = retval[name] = [] l.append(value) return retval def _get_http_headers(req_env): retval = {} for k, v in req_env.items(): if k.startswith("HTTP_"): key = k[5:].lower() val = [v] retval[key]= val logger.debug("Add http header %r = %r", key, val) return retval def _gen_http_headers(headers): retval = [] for k,v in headers.items(): if isinstance(v, (list, tuple)): for v2 in v: retval.append((k, v2)) else: retval.append((k, v)) return retval class WsgiTransportContext(HttpTransportContext): """The class that is used in the transport attribute of the :class:`WsgiMethodContext` class.""" def __init__(self, parent, transport, req_env, content_type): super(WsgiTransportContext, self).__init__(parent, transport, req_env, content_type) self.req_env = self.req """WSGI Request environment""" self.req_method = req_env.get('REQUEST_METHOD', None) """HTTP Request verb, as a convenience to users.""" self.headers = _get_http_headers(self.req_env) def get_url(self): return _reconstruct_url(self.req_env) def get_path(self): return self.req_env['PATH_INFO'] def get_path_and_qs(self): retval = quote(self.req_env.get('PATH_INFO', '')) qs = self.req_env.get('QUERY_STRING', None) if qs is not None: retval += '?' + qs return retval def get_cookie(self, key): cookie_string = self.req_env.get('HTTP_COOKIE', None) if cookie_string is None: return cookie = SimpleCookie() cookie.load(cookie_string) return cookie.get(key, None).value def get_request_method(self): return self.req['REQUEST_METHOD'].upper() def get_request_content_type(self): return self.req.get("CONTENT_TYPE", None) def get_peer(self): addr, port = address_parser.get_ip(self.req),\ address_parser.get_port(self.req) if address_parser.is_valid_ipv4(addr): return Address(type=Address.TCP4, host=addr, port=port) if address_parser.is_valid_ipv6(addr): return Address(type=Address.TCP6, host=addr, port=port) class WsgiMethodContext(HttpMethodContext): """The WSGI-Specific method context. WSGI-Specific information is stored in the transport attribute using the :class:`WsgiTransportContext` class. """ TransportContext = None HttpTransportContext = WsgiTransportContext class WsgiApplication(HttpBase): """A `PEP-3333 `_ compliant callable class. If you want to have a hard-coded URL in the wsdl document, this is how to do it: :: wsgi_app = WsgiApplication(...) wsgi_app.doc.wsdl11.build_interface_document("http://example.com") This is not strictly necessary -- if you don't do this, Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests. However, if you want to make sure you only have this url on the WSDL, this is how to do it. Note that if your client takes the information in the Wsdl document seriously (not all do), all requests will go to the designated url above even when you get the Wsdl from another location, which can make testing a bit difficult. Use in moderation. Supported events: * ``wsdl`` Called right before the wsdl data is returned to the client. * ``wsdl_exception`` Called right after an exception is thrown during wsdl generation. The exception object is stored in ctx.transport.wsdl_error attribute. * ``wsgi_call`` Called first when the incoming http request is identified as a rpc request. * ``wsgi_return`` Called right before the output stream is returned to the WSGI handler. * ``wsgi_exception`` Called right before returning the exception to the client. * ``wsgi_close`` Called after the whole data has been returned to the client. It's called both from success and error cases. """ def __init__(self, app, chunked=True, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): super(WsgiApplication, self).__init__(app, chunked, max_content_length, block_length) self._mtx_build_interface_document = threading.Lock() self._wsdl = None if self.doc.wsdl11 is not None: self._wsdl = self.doc.wsdl11.get_interface_document() def __call__(self, req_env, start_response, wsgi_url=None): """This method conforms to the WSGI spec for callable wsgi applications (PEP 333). It looks in environ['wsgi.input'] for a fully formed rpc message envelope, will deserialize the request parameters and call the method on the object returned by the get_handler() method. """ url = wsgi_url if url is None: url = _reconstruct_url(req_env).split('.wsdl')[0] if self.is_wsdl_request(req_env): # Format the url for location url = url.split('?')[0].split('.wsdl')[0] return self.handle_wsdl_request(req_env, start_response, url) else: return self.handle_rpc(req_env, start_response) def is_wsdl_request(self, req_env): # Get the wsdl for the service. Assume path_info matches pattern: # /stuff/stuff/stuff/serviceName.wsdl or # /stuff/stuff/stuff/serviceName/?wsdl return ( req_env['REQUEST_METHOD'].upper() == 'GET' and ( ( 'QUERY_STRING' in req_env and req_env['QUERY_STRING'].split('=')[0].lower() == 'wsdl' ) or req_env['PATH_INFO'].endswith('.wsdl') ) ) def handle_wsdl_request(self, req_env, start_response, url): ctx = WsgiMethodContext(self, req_env, 'text/xml; charset=utf-8') if self.doc.wsdl11 is None: start_response(HTTP_404, _gen_http_headers(ctx.transport.resp_headers)) return [HTTP_404] if self._wsdl is None: self._wsdl = self.doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl if ctx.transport.wsdl is None: try: self._mtx_build_interface_document.acquire() ctx.transport.wsdl = self._wsdl if ctx.transport.wsdl is None: self.doc.wsdl11.build_interface_document(url) ctx.transport.wsdl = self._wsdl = \ self.doc.wsdl11.get_interface_document() except Exception as e: logger.exception(e) ctx.transport.wsdl_error = e self.event_manager.fire_event('wsdl_exception', ctx) start_response(HTTP_500, _gen_http_headers(ctx.transport.resp_headers)) return [HTTP_500] finally: self._mtx_build_interface_document.release() self.event_manager.fire_event('wsdl', ctx) ctx.transport.resp_headers['Content-Length'] = \ str(len(ctx.transport.wsdl)) start_response(HTTP_200, _gen_http_headers(ctx.transport.resp_headers)) retval = ctx.transport.wsdl ctx.close() return [retval] def handle_error(self, p_ctx, others, error, start_response): """Serialize errors to an iterable of strings and return them. :param p_ctx: Primary (non-aux) context. :param others: List if auxiliary contexts (can be empty). :param error: One of ctx.{in,out}_error. :param start_response: See the WSGI spec for more info. """ if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = \ p_ctx.out_protocol.fault_to_http_response_code(error) self.get_out_string(p_ctx) # consume the generator to get the length p_ctx.out_string = list(p_ctx.out_string) p_ctx.transport.resp_headers['Content-Length'] = \ str(sum((len(s) for s in p_ctx.out_string))) self.event_manager.fire_event('wsgi_exception', p_ctx) start_response(p_ctx.transport.resp_code, _gen_http_headers(p_ctx.transport.resp_headers)) try: process_contexts(self, others, p_ctx, error=error) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) return chain(p_ctx.out_string, self.__finalize(p_ctx)) def handle_rpc(self, req_env, start_response): initial_ctx = WsgiMethodContext(self, req_env, self.app.out_protocol.mime_type) self.event_manager.fire_event('wsgi_call', initial_ctx) initial_ctx.in_string, in_string_charset = \ self.__reconstruct_wsgi_request(req_env) contexts = self.generate_contexts(initial_ctx, in_string_charset) p_ctx, others = contexts[0], contexts[1:] # TODO: rate limiting p_ctx.active = True if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error, start_response) self.get_in_object(p_ctx) if p_ctx.in_error: logger.error(p_ctx.in_error) return self.handle_error(p_ctx, others, p_ctx.in_error, start_response) self.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error, start_response) assert p_ctx.out_object is not None g = next(iter(p_ctx.out_object)) is_generator = len(p_ctx.out_object) == 1 and isgenerator(g) # if the out_object is a generator function, this hack makes the user # code run until first yield, which lets it set response headers and # whatnot before calling start_response. It's important to run this # here before serialization as the user function can also set output # protocol. Is there a better way? if is_generator: first_obj = next(g) p_ctx.out_object = ( chain((first_obj,), g), ) if p_ctx.transport.resp_code is None: p_ctx.transport.resp_code = HTTP_200 try: self.get_out_string(p_ctx) except Exception as e: logger.exception(e) p_ctx.out_error = Fault('Server', get_fault_string_from_exception(e)) return self.handle_error(p_ctx, others, p_ctx.out_error, start_response) if isinstance(p_ctx.out_protocol, HttpRpc) and \ p_ctx.out_header_doc is not None: p_ctx.transport.resp_headers.update(p_ctx.out_header_doc) if p_ctx.descriptor and p_ctx.descriptor.mtom: # when there is more than one return type, the result is # encapsulated inside a list. when there's just one, the result # is returned in a non-encapsulated form. the apply_mtom always # expects the objects to be inside an iterable, hence the # following test. out_type_info = p_ctx.descriptor.out_message._type_info if len(out_type_info) == 1: p_ctx.out_object = [p_ctx.out_object] p_ctx.transport.resp_headers, p_ctx.out_string = apply_mtom( p_ctx.transport.resp_headers, p_ctx.out_string, p_ctx.descriptor.out_message._type_info.values(), p_ctx.out_object, ) self.event_manager.fire_event('wsgi_return', p_ctx) if self.chunked: # the user has not set a content-length, so we delete it as the # input is just an iterable. if 'Content-Length' in p_ctx.transport.resp_headers: del p_ctx.transport.resp_headers['Content-Length'] else: p_ctx.out_string = [''.join(p_ctx.out_string)] try: len(p_ctx.out_string) p_ctx.transport.resp_headers['Content-Length'] = \ str(sum([len(a) for a in p_ctx.out_string])) except TypeError: pass start_response(p_ctx.transport.resp_code, _gen_http_headers(p_ctx.transport.resp_headers)) retval = chain(p_ctx.out_string, self.__finalize(p_ctx)) try: process_contexts(self, others, p_ctx, error=None) except Exception as e: # Report but ignore any exceptions from auxiliary methods. logger.exception(e) return retval def __finalize(self, p_ctx): p_ctx.close() self.event_manager.fire_event('wsgi_close', p_ctx) return () def __reconstruct_wsgi_request(self, http_env): """Reconstruct http payload using information in the http header.""" content_type = http_env.get("CONTENT_TYPE") charset = None if content_type is not None: # fyi, here's what the parse_header function returns: # >>> import cgi; cgi.parse_header("text/xml; charset=utf-8") # ('text/xml', {'charset': 'utf-8'}) content_type = cgi.parse_header(content_type) charset = content_type[1].get('charset', None) return self.__wsgi_input_to_iterable(http_env), charset def __wsgi_input_to_iterable(self, http_env): istream = http_env.get('wsgi.input') length = str(http_env.get('CONTENT_LENGTH', self.max_content_length)) if len(length) == 0: length = 0 else: length = int(length) if length > self.max_content_length: raise RequestTooLongError() bytes_read = 0 while bytes_read < length: bytes_to_read = min(self.block_length, length - bytes_read) if bytes_to_read + bytes_read > self.max_content_length: raise RequestTooLongError() data = istream.read(bytes_to_read) if data is None or len(data) == 0: break bytes_read += len(data) yield data def decompose_incoming_envelope(self, prot, ctx, message): """This function is only called by the HttpRpc protocol to have the wsgi environment parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. """ params = {} wsgi_env = ctx.in_document if self.has_patterns: # http://legacy.python.org/dev/peps/pep-0333/#url-reconstruction domain = wsgi_env.get('HTTP_HOST', None) if domain is None: domain = wsgi_env['SERVER_NAME'] else: domain = domain.partition(':')[0] # strip port info params = self.match_pattern(ctx, wsgi_env.get('REQUEST_METHOD', ''), wsgi_env.get('PATH_INFO', ''), domain, ) if ctx.method_request_string is None: ctx.method_request_string = '{%s}%s' % ( prot.app.interface.get_tns(), wsgi_env['PATH_INFO'].split('/')[-1]) logger.debug("%sMethod name: %r%s" % (LIGHT_GREEN, ctx.method_request_string, END_COLOR)) ctx.in_header_doc = ctx.transport.headers ctx.in_body_doc = _parse_qs(wsgi_env['QUERY_STRING']) for k, v in params.items(): if k in ctx.in_body_doc: ctx.in_body_doc[k].extend(v) else: ctx.in_body_doc[k] = list(v) verb = wsgi_env['REQUEST_METHOD'].upper() if verb in ('POST', 'PUT', 'PATCH'): stream, form, files = parse_form_data(wsgi_env, stream_factory=prot.stream_factory) for k, v in form.lists(): val = ctx.in_body_doc.get(k, []) val.extend(v) ctx.in_body_doc[k] = val for k, v in files.items(): val = ctx.in_body_doc.get(k, []) mime_type = v.headers.get('Content-Type', 'application/octet-stream') path = getattr(v.stream, 'name', None) if path is None: val.append(File.Value(name=v.filename, type=mime_type, data=[v.stream.getvalue()])) else: v.stream.seek(0) val.append(File.Value(name=v.filename, type=mime_type, path=path, handle=v.stream)) ctx.in_body_doc[k] = val for k, v in ctx.in_body_doc.items(): if v == ['']: ctx.in_body_doc[k] = [None] spyne-spyne-2.14.0/spyne/server/zeromq.py000066400000000000000000000121721417664205300204360ustar00rootroot00000000000000# # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The ``spyne.server.zeromq`` module contains a server implementation that uses ZeroMQ (zmq.REP) as transport. """ import threading import zmq from spyne.auxproc import process_contexts from spyne.context import MethodContext from spyne.server import ServerBase class ZmqMethodContext(MethodContext): def __init__(self, app): super(ZmqMethodContext, self).__init__(app, MethodContext.SERVER) self.transport.type = 'zmq' class ZeroMQServer(ServerBase): """The ZeroMQ server transport.""" transport = 'http://rfc.zeromq.org/' def __init__(self, app, app_url, wsdl_url=None, ctx=None, socket=None): if ctx and socket and ctx is not socket.context: raise ValueError("ctx should be the same as socket.context") super(ZeroMQServer, self).__init__(app) self.app_url = app_url self.wsdl_url = wsdl_url if ctx: self.ctx = ctx elif socket: self.ctx = socket.context else: self.ctx = zmq.Context() if socket: self.zmq_socket = socket else: self.zmq_socket = self.ctx.socket(zmq.REP) self.zmq_socket.bind(app_url) def __handle_wsdl_request(self): return self.app.get_interface_document(self.url) # FIXME: Add suport for binary-only transports def generate_contexts(self, ctx, in_string_charset='utf8'): return super(ZeroMQServer, self).generate_contexts(ctx, in_string_charset=in_string_charset) def serve_forever(self): """Runs the ZeroMQ server.""" while True: error = None initial_ctx = ZmqMethodContext(self) initial_ctx.in_string = [self.zmq_socket.recv()] contexts = self.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] # TODO: Rate limiting p_ctx.active = True if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error error = p_ctx.in_error else: self.get_in_object(p_ctx) if p_ctx.in_error: p_ctx.out_object = p_ctx.in_error error = p_ctx.in_error else: self.get_out_object(p_ctx) if p_ctx.out_error: p_ctx.out_object = p_ctx.out_error error = p_ctx.out_error self.get_out_string(p_ctx) process_contexts(self, others, error) self.zmq_socket.send(b''.join(p_ctx.out_string)) p_ctx.close() class ZeroMQThreadPoolServer(object): """Create a ZeroMQ server transport with several background workers, allowing asynchronous calls. More details on the pattern http://zguide.zeromq.org/page:all#Shared-Queue-DEALER-and-ROUTER-sockets""" def __init__(self, app, app_url, pool_size, wsdl_url=None, ctx=None, socket=None): if ctx and socket and ctx is not socket.context: raise ValueError("ctx should be the same as socket.context") self.app = app if ctx: self.ctx = ctx elif socket: self.ctx = socket.context else: self.ctx = zmq.Context() if socket: self.frontend = socket else: self.frontend = self.ctx.socket(zmq.ROUTER) self.frontend.bind(app_url) be_url = 'inproc://{tns}.{name}'.format(tns=self.app.tns, name=self.app.name) self.pool = [] self.background_jobs = [] for i in range(pool_size): worker, job = self.create_worker(i, be_url) self.pool.append(worker) self.background_jobs.append(job) self.backend = self.ctx.socket(zmq.DEALER) self.backend.bind(be_url) def create_worker(self, i, be_url): socket = self.ctx.socket(zmq.REP) socket.connect(be_url) worker = ZeroMQServer(self.app, be_url, socket=socket) job = threading.Thread(target=worker.serve_forever) job.daemon = True return worker, job def serve_forever(self): """Runs the ZeroMQ server.""" for job in self.background_jobs: job.start() zmq.device(zmq.QUEUE, self.frontend, self.backend) # We never get here... self.frontend.close() self.backend.close() spyne-spyne-2.14.0/spyne/service.py000066400000000000000000000200511417664205300172460ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ This module contains the :class:`Service` class and its helper objects. """ import logging logger = logging.getLogger(__name__) from spyne.util.six.moves.collections_abc import Sequence from spyne.evmgr import EventManager from spyne.util import six from spyne.util.oset import oset class ServiceBaseMeta(type): """Adds event managers.""" def __init__(self, cls_name, cls_bases, cls_dict): super(ServiceBaseMeta, self).__init__(cls_name, cls_bases, cls_dict) self.public_methods = {} self.event_manager = EventManager(self, self.__get_base_event_handlers(cls_bases)) def __get_base_event_handlers(self, cls_bases): handlers = {} for base in cls_bases: evmgr = getattr(base, 'event_manager', None) if evmgr is None: continue for k, v in evmgr.handlers.items(): handler = handlers.get(k, oset()) for h in v: handler.add(h) handlers[k] = handler return handlers class ServiceMeta(ServiceBaseMeta): """Creates the :class:`spyne.MethodDescriptor` objects by iterating over tagged methods. """ def __init__(self, cls_name, cls_bases, cls_dict): super(ServiceMeta, self).__init__(cls_name, cls_bases, cls_dict) self.__has_aux_methods = self.__aux__ is not None has_nonaux_methods = None for k, v in cls_dict.items(): if not hasattr(v, '_is_rpc'): continue descriptor = v(_default_function_name=k, _service_class=self) # these two lines are needed for staticmethod wrapping to work setattr(self, k, staticmethod(descriptor.function)) descriptor.reset_function(getattr(self, k)) try: getattr(self, k).descriptor = descriptor except AttributeError: pass # FIXME: this fails with builtins. Temporary hack while we # investigate whether we really need this or not self.public_methods[k] = descriptor if descriptor.aux is None and self.__aux__ is None: has_nonaux_methods = True else: self.__has_aux_methods = True if self.__has_aux_methods and has_nonaux_methods: raise Exception("You can't mix primary and " "auxiliary methods in a single service definition.") def is_auxiliary(self): return self.__has_aux_methods # FIXME: To be renamed to ServiceBase in Spyne 3 @six.add_metaclass(ServiceBaseMeta) class ServiceBaseBase(object): __in_header__ = None """The incoming header object that the methods under this service definition accept.""" __out_header__ = None """The outgoing header object that the methods under this service definition accept.""" __service_name__ = None """The name of this service definition as exposed in the interface document. Defaults to the class name.""" __service_module__ = None """This is used for internal idenfitication of the service class, to override the ``__module__`` attribute.""" __port_types__ = () """WSDL-Specific portType mappings""" __aux__ = None """The auxiliary method type. When set, the ``aux`` property of every method defined under this service is set to this value. The _aux flag in the @srpc decorator overrides this.""" @classmethod def get_service_class_name(cls): return cls.__name__ @classmethod def get_service_name(cls): if cls.__service_name__ is None: return cls.__name__ else: return cls.__service_name__ @classmethod def get_service_module(cls): if cls.__service_module__ is None: return cls.__module__ else: return cls.__service_module__ @classmethod def get_internal_key(cls): return "%s.%s" % (cls.get_service_module(), cls.get_service_name()) @classmethod def get_port_types(cls): return cls.__port_types__ @classmethod def _has_callbacks(cls): """Determines if this service definition has callback methods or not.""" for method in cls.public_methods.values(): if method.is_callback: return True return False @classmethod def get_context(cls): """Returns a user defined context. Override this in your ServiceBase subclass to customize context generation.""" return None @classmethod def call_wrapper(cls, ctx, args=None): """Called in place of the original method call. You can override this to do your own exception handling. :param ctx: The method context. The overriding function must call this function by convention. """ if ctx.function is not None: if args is None: args = ctx.in_object assert not isinstance(args, six.string_types) # python3 wants a proper sequence as *args if not isinstance(args, Sequence): args = tuple(args) if not ctx.descriptor.no_ctx: args = (ctx,) + tuple(args) return ctx.function(*args) @classmethod def initialize(cls, app): pass @six.add_metaclass(ServiceMeta) class Service(ServiceBaseBase): """The ``Service`` class is the base class for all service definitions. The convention is to have public methods defined under a subclass of this class along with common properties of public methods like header classes or auxiliary processors. The :func:`spyne.decorator.srpc` decorator or its wrappers should be used to flag public methods. This class is designed to be subclassed just once. You're supposed to combine Service subclasses in order to get the public method mix you want. It is a natural abstract base class, because it's of no use without any method definitions, hence the 'Base' suffix in the name. This class supports the following events: * ``method_call`` Called right before the service method is executed * ``method_return_object`` Called right after the service method is executed * ``method_exception_object`` Called when an exception occurred in a service method, before the exception is serialized. * ``method_accept_document`` Called by the transport right after the incoming stream is parsed to the incoming protocol's document type. * ``method_return_document`` Called by the transport right after the outgoing object is serialized to the outgoing protocol's document type. * ``method_exception_document`` Called by the transport right before the outgoing exception object is serialized to the outgoing protocol's document type. * ``method_return_string`` Called by the transport right before passing the return string to the client. * ``method_exception_string`` Called by the transport right before passing the exception string to the client. """ # FIXME: To be deleted in Spyne 3 ServiceBase = Service spyne-spyne-2.14.0/spyne/store/000077500000000000000000000000001417664205300163725ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/store/__init__.py000066400000000000000000000014601417664205300205040ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """This is the spyne storage package.""" spyne-spyne-2.14.0/spyne/store/relational/000077500000000000000000000000001417664205300205245ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/store/relational/__init__.py000066400000000000000000000025551417664205300226440ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """A Postgresql serializer for Spyne objects. Uses SQLAlchemy for mapping objects to relations. """ from spyne.store.relational._base import add_column from spyne.store.relational._base import gen_sqla_info from spyne.store.relational._base import gen_spyne_info from spyne.store.relational._base import get_pk_columns from spyne.store.relational.document import PGXml, PGObjectXml, PGHtml, \ PGJson, PGJsonB, PGObjectJson, PGFileJson from spyne.store.relational.simple import PGLTree, PGLQuery, PGLTxtQuery from spyne.store.relational.spatial import PGGeometry from spyne.store.relational import override spyne-spyne-2.14.0/spyne/store/relational/_base.py000066400000000000000000001206511417664205300221540ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import, print_function import logging logger = logging.getLogger(__name__) import sqlalchemy try: import simplejson as json except ImportError: import json from os.path import isabs from inspect import isclass from sqlalchemy import event from sqlalchemy.schema import Column from sqlalchemy.schema import Index from sqlalchemy.schema import Table from sqlalchemy.schema import ForeignKey from sqlalchemy.dialects.postgresql import FLOAT from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION from sqlalchemy.dialects.postgresql.base import PGUuid, PGInet from sqlalchemy.orm import relationship from sqlalchemy.orm import mapper from sqlalchemy.ext.associationproxy import association_proxy # TODO: find the latest way of checking whether a class is already mapped try: from sqlalchemy.orm import mapperlib _mapper_registries = mapperlib._mapper_registries except (ImportError, AttributeError): from sqlalchemy.orm import _mapper_registry as _mapper_registries from spyne.store.relational.simple import PGLTree from spyne.store.relational.document import PGXml, PGObjectXml, PGObjectJson, \ PGFileJson, PGJsonB, PGHtml, PGJson from spyne.store.relational.spatial import PGGeometry # internal types from spyne.model.enum import EnumBase from spyne.model.complex import XmlModifier # Config types from spyne.model import xml as c_xml from spyne.model import json as c_json from spyne.model import jsonb as c_jsonb from spyne.model import table as c_table from spyne.model import msgpack as c_msgpack from spyne.model.binary import HybridFileStore # public types from spyne.model import SimpleModel, Enum, Array, ComplexModelBase, \ Any, AnyDict, AnyXml, AnyHtml, \ Date, Time, DateTime, Duration, \ ByteArray, String, Unicode, Uuid, Boolean, \ Point, Line, Polygon, MultiPoint, MultiLine, MultiPolygon, \ Float, Double, Decimal, \ Integer, Integer8, Integer16, Integer32, Integer64, \ UnsignedInteger, UnsignedInteger8, UnsignedInteger16, UnsignedInteger32, \ UnsignedInteger64, \ Ipv6Address, Ipv4Address, IpAddress, \ File, Ltree from spyne.util import sanitize_args # Inheritance type constants. class _SINGLE: pass class _JOINED: pass _sq2sp_type_map = { # we map float => double because sqla doesn't # distinguish between floats and doubles. sqlalchemy.Float: Double, sqlalchemy.FLOAT: Double, sqlalchemy.Numeric: Decimal, sqlalchemy.NUMERIC: Decimal, sqlalchemy.BigInteger: Integer64, sqlalchemy.BIGINT: Integer64, sqlalchemy.Integer: Integer32, sqlalchemy.INTEGER: Integer32, sqlalchemy.SmallInteger: Integer16, sqlalchemy.SMALLINT: Integer16, sqlalchemy.LargeBinary: ByteArray, sqlalchemy.Boolean: Boolean, sqlalchemy.BOOLEAN: Boolean, sqlalchemy.DateTime: DateTime, sqlalchemy.TIMESTAMP: DateTime, sqlalchemy.dialects.postgresql.base.TIMESTAMP: DateTime, sqlalchemy.DATETIME: DateTime, sqlalchemy.dialects.postgresql.base.INTERVAL: Duration, sqlalchemy.Date: Date, sqlalchemy.DATE: Date, sqlalchemy.Time: Time, sqlalchemy.TIME: Time, PGUuid: Uuid, PGLTree: Ltree, PGInet: IpAddress, } sqlalchemy_BINARY = \ getattr(sqlalchemy, 'Binary', getattr(sqlalchemy, 'BINARY', None)) if sqlalchemy_BINARY is not None: _sq2sp_type_map[sqlalchemy_BINARY] = ByteArray # this needs to be called whenever a new column is instantiated. def _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs=None, col=None): # cls is the parent class of v if subcls.Attributes.nullable == False and cls.__extends__ is None: if col is None: col_kwargs['nullable'] = False else: col.nullable = False if subcls.Attributes.db_default is not None: if col is None: col_kwargs['default'] = subcls.Attributes.db_default else: col.default = subcls.Attributes.db_default def _get_sqlalchemy_type(cls): db_type = cls.Attributes.db_type if db_type is not None: return db_type # must be above Unicode, because Ltree is Unicode's subclass if issubclass(cls, Ltree): return PGLTree # must be above Unicode, because Ip*Address is Unicode's subclass if issubclass(cls, (IpAddress, Ipv4Address, Ipv6Address)): return PGInet # must be above Unicode, because Uuid is Unicode's subclass if issubclass(cls, Uuid): return PGUuid(as_uuid=True) # must be above Unicode, because Point is Unicode's subclass if issubclass(cls, Point): return PGGeometry("POINT", dimension=cls.Attributes.dim) # must be above Unicode, because Line is Unicode's subclass if issubclass(cls, Line): return PGGeometry("LINESTRING", dimension=cls.Attributes.dim) # must be above Unicode, because Polygon is Unicode's subclass if issubclass(cls, Polygon): return PGGeometry("POLYGON", dimension=cls.Attributes.dim) # must be above Unicode, because MultiPoint is Unicode's subclass if issubclass(cls, MultiPoint): return PGGeometry("MULTIPOINT", dimension=cls.Attributes.dim) # must be above Unicode, because MultiLine is Unicode's subclass if issubclass(cls, MultiLine): return PGGeometry("MULTILINESTRING", dimension=cls.Attributes.dim) # must be above Unicode, because MultiPolygon is Unicode's subclass if issubclass(cls, MultiPolygon): return PGGeometry("MULTIPOLYGON", dimension=cls.Attributes.dim) # must be above Unicode, because String is Unicode's subclass if issubclass(cls, String): if cls.Attributes.max_len == String.Attributes.max_len: # Default is arbitrary-length return sqlalchemy.Text else: return sqlalchemy.String(cls.Attributes.max_len) if issubclass(cls, Unicode): if cls.Attributes.max_len == Unicode.Attributes.max_len: # Default is arbitrary-length return sqlalchemy.UnicodeText else: return sqlalchemy.Unicode(cls.Attributes.max_len) if issubclass(cls, EnumBase): return sqlalchemy.Enum(*cls.__values__, name=cls.__type_name__) if issubclass(cls, AnyXml): return PGXml if issubclass(cls, AnyHtml): return PGHtml if issubclass(cls, (Any, AnyDict)): sa = cls.Attributes.store_as if sa is None: return None if isinstance(sa, c_json): return PGJson if isinstance(sa, c_jsonb): return PGJsonB raise NotImplementedError(dict(cls=cls, store_as=sa)) if issubclass(cls, ByteArray): return sqlalchemy.LargeBinary if issubclass(cls, (Integer64, UnsignedInteger64)): return sqlalchemy.BigInteger if issubclass(cls, (Integer32, UnsignedInteger32)): return sqlalchemy.Integer if issubclass(cls, (Integer16, UnsignedInteger16)): return sqlalchemy.SmallInteger if issubclass(cls, (Integer8, UnsignedInteger8)): return sqlalchemy.SmallInteger if issubclass(cls, Float): return FLOAT if issubclass(cls, Double): return DOUBLE_PRECISION if issubclass(cls, (Integer, UnsignedInteger)): return sqlalchemy.DECIMAL if issubclass(cls, Decimal): return sqlalchemy.DECIMAL if issubclass(cls, Boolean): if cls.Attributes.store_as is bool: return sqlalchemy.Boolean if cls.Attributes.store_as is int: return sqlalchemy.SmallInteger raise ValueError("Boolean.store_as has invalid value %r" % cls.Attributes.store_as) if issubclass(cls, Date): return sqlalchemy.Date if issubclass(cls, DateTime): if cls.Attributes.timezone is None: if cls.Attributes.as_timezone is None: return sqlalchemy.DateTime(timezone=True) else: return sqlalchemy.DateTime(timezone=False) else: return sqlalchemy.DateTime(timezone=cls.Attributes.timezone) if issubclass(cls, Time): return sqlalchemy.Time if issubclass(cls, Duration): return sqlalchemy.dialects.postgresql.base.INTERVAL if issubclass(cls, XmlModifier): retval = _get_sqlalchemy_type(cls.type) return retval def _get_col_o2o(parent, subname, subcls, fk_col_name, deferrable=None, initially=None, ondelete=None, onupdate=None): """Gets key and child type and returns a column that points to the primary key of the child. """ assert subcls.Attributes.table_name is not None, \ "%r has no table name." % subcls col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(parent, subcls, col_kwargs) # get pkeys from child class pk_column, = get_pk_columns(subcls) # FIXME: Support multi-col keys pk_key, pk_spyne_type = pk_column pk_sqla_type = _get_sqlalchemy_type(pk_spyne_type) # generate a fk to it from the current object (cls) if 'name' in col_kwargs: colname = col_kwargs.pop('name') else: colname = subname if fk_col_name is None: fk_col_name = colname + "_" + pk_key assert fk_col_name != colname, \ "The column name for the foreign key must be different from the " \ "column name for the object itself." fk = ForeignKey( '%s.%s' % (subcls.Attributes.table_name, pk_key), use_alter=True, name='%s_%s_fkey' % (subcls.Attributes.table_name, fk_col_name), deferrable=deferrable, initially=initially, ondelete=ondelete, onupdate=onupdate, ) return Column(fk_col_name, pk_sqla_type, fk, **col_kwargs) def _get_col_o2m(cls, fk_col_name, deferrable=None, initially=None, ondelete=None, onupdate=None): """Gets the parent class and returns a column that points to the primary key of the parent. """ assert cls.Attributes.table_name is not None, "%r has no table name." % cls col_args, col_kwargs = sanitize_args(cls.Attributes.sqla_column_args) # get pkeys from current class pk_column, = get_pk_columns(cls) # FIXME: Support multi-col keys pk_key, pk_spyne_type = pk_column pk_sqla_type = _get_sqlalchemy_type(pk_spyne_type) # generate a fk from child to the current class if fk_col_name is None: fk_col_name = '_'.join([cls.Attributes.table_name, pk_key]) # we jump through all these hoops because we must instantiate the Column # only after we're sure that it doesn't already exist and also because # tinkering with functors is always fun :) yield [(fk_col_name, pk_sqla_type)] fk = ForeignKey('%s.%s' % (cls.Attributes.table_name, pk_key), deferrable=deferrable, initially=initially, ondelete=ondelete, onupdate=onupdate) col = Column(fk_col_name, pk_sqla_type, fk, **col_kwargs) yield col def _get_cols_m2m(cls, k, child, fk_left_col_name, fk_right_col_name, fk_left_deferrable, fk_left_initially, fk_right_deferrable, fk_right_initially, fk_left_ondelete, fk_left_onupdate, fk_right_ondelete, fk_right_onupdate): """Gets the parent and child classes and returns foreign keys to both tables. These columns can be used to create a relation table.""" col_info, left_col = _get_col_o2m(cls, fk_left_col_name, ondelete=fk_left_ondelete, onupdate=fk_left_onupdate, deferrable=fk_left_deferrable, initially=fk_left_initially) right_col = _get_col_o2o(cls, k, child, fk_right_col_name, ondelete=fk_right_ondelete, onupdate=fk_right_onupdate, deferrable=fk_right_deferrable, initially=fk_right_initially) left_col.primary_key = right_col.primary_key = True return left_col, right_col class _FakeTable(object): def __init__(self, name): self.name = name self.c = {} self.columns = [] self.indexes = [] def append_column(self, col): self.columns.append(col) self.c[col.name] = col def _gen_index_info(table, col, k, v): """ :param table: sqla table :param col: sqla col :param k: field name (not necessarily == k) :param v: spyne type """ unique = v.Attributes.unique index = v.Attributes.index if unique and not index: index = True try: index_name, index_method = index except (TypeError, ValueError): index_name = "%s_%s%s" % (table.name, k, '_unique' if unique else '') index_method = index if index in (False, None): return if index is True: index_args = (index_name, col), dict(unique=unique) else: index_args = (index_name, col), dict(unique=unique, postgresql_using=index_method) if isinstance(table, _FakeTable): table.indexes.append(index_args) else: indexes = dict([(idx.name, idx) for idx in col.table.indexes]) existing_idx = indexes.get(index_name, None) if existing_idx is None: Index(*index_args[0], **index_args[1]) else: assert existing_idx.unique == unique, \ "Uniqueness flag differ between existing and current values. " \ "Existing: {!r}, New: {!r}".format(existing_idx.unique, unique) existing_val = existing_idx.kwargs.get('postgresql_using') assert existing_val == index_method, \ "Indexing methods differ between existing and current index " \ "directives. Existing: {!r}, New: {!r}".format( existing_val, index_method) def _check_inheritance(cls, cls_bases): table_name = cls.Attributes.table_name inc = [] inheritance = None base_class = getattr(cls, '__extends__', None) if base_class is None: for b in cls_bases: if getattr(b, '_type_info', None) is not None and b.__mixin__: base_class = b if base_class is not None: base_table_name = base_class.Attributes.table_name if base_table_name is not None: if base_table_name == table_name: inheritance = _SINGLE else: inheritance = _JOINED raise NotImplementedError("Joined table inheritance is not yet " "implemented.") # check whether the base classes are already mapped base_mapper = None if base_class is not None: base_mapper = base_class.Attributes.sqla_mapper if base_mapper is None: for b in cls_bases: bm = _mapper_registries.get(b, None) if bm is not None: assert base_mapper is None, "There can be only one base mapper." base_mapper = bm inheritance = _SINGLE return inheritance, base_class, base_mapper, inc def _check_table(cls): table_name = cls.Attributes.table_name metadata = cls.Attributes.sqla_metadata # check whether the object already has a table table = None if table_name in metadata.tables: table = metadata.tables[table_name] else: # We need FakeTable because table_args can contain all sorts of stuff # that can require a fully-constructed table, and we don't have that # information here yet. table = _FakeTable(table_name) return table def _add_simple_type(cls, props, table, subname, subcls, sqla_type): col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) mp = getattr(subcls.Attributes, 'mapper_property', None) if 'name' in col_kwargs: colname = col_kwargs.pop('name') else: colname = subname if not subcls.Attributes.exc_db: if colname in table.c: col = table.c[colname] else: col = Column(colname, sqla_type, *col_args, **col_kwargs) table.append_column(col) _gen_index_info(table, col, subname, subcls) if not subcls.Attributes.exc_mapper: props[subname] = col elif mp is not None: props[subname] = mp def _gen_array_m2m(cls, props, subname, arrser, storage): """Generates a relational many-to-many array. :param cls: The class that owns the field :param props: SQLAlchemy Mapper properties :param subname: Field name :param arrser: Array serializer, ie the __orig__ of the class inside the Array object :param storage: The storage configuration object passed to the store_as attribute. """ metadata = cls.Attributes.sqla_metadata col_own, col_child = _get_cols_m2m(cls, subname, arrser, storage.left, storage.right, storage.fk_left_deferrable, storage.fk_left_initially, storage.fk_right_deferrable, storage.fk_right_initially, storage.fk_left_ondelete, storage.fk_left_onupdate, storage.fk_right_ondelete, storage.fk_right_onupdate) storage.left = col_own.key storage.right = col_child.key # noinspection PySimplifyBooleanCheck because literal True means # "generate table name automatically" here if storage.multi is True: rel_table_name = '_'.join([cls.Attributes.table_name, subname]) else: rel_table_name = storage.multi if rel_table_name in metadata.tables: rel_t = metadata.tables[rel_table_name] col_own_existing = rel_t.c.get(col_own.key, None) assert col_own_existing is not None if col_own_existing is not None: assert col_own.type.__class__ == col_own_existing.type.__class__ col_child_existing = rel_t.c.get(col_child.key, None) if col_child_existing is None: rel_t.append_column(col_child) else: assert col_child.type.__class__ == col_child_existing.type.__class__ else: rel_t = Table(rel_table_name, metadata, *(col_own, col_child)) own_t = cls.Attributes.sqla_table rel_kwargs = dict( lazy=storage.lazy, backref=storage.backref, cascade=storage.cascade, order_by=storage.order_by, back_populates=storage.back_populates, ) if storage.explicit_join: # Specify primaryjoin and secondaryjoin when requested. # There are special cases when sqlalchemy can't figure it out by itself. # this is where we help it when we can. # e.g.: http://sqlalchemy.readthedocs.org/en/rel_1_0/orm/join_conditions.html#self-referential-many-to-many-relationship assert own_t is not None and len(get_pk_columns(cls)) > 0 # FIXME: support more than one pk (col_pk_key, _), = get_pk_columns(cls) col_pk = own_t.c[col_pk_key] rel_kwargs.update(dict( secondary=rel_t, primaryjoin=(col_pk == rel_t.c[col_own.key]), secondaryjoin=(col_pk == rel_t.c[col_child.key]), )) if storage.single_parent is not None: rel_kwargs['single_parent'] = storage.single_parent props[subname] = relationship(arrser, **rel_kwargs) else: rel_kwargs.update(dict( secondary=rel_t, )) if storage.single_parent is not None: rel_kwargs['single_parent'] = storage.single_parent props[subname] = relationship(arrser, **rel_kwargs) def _gen_array_simple(cls, props, subname, arrser_cust, storage): """Generate an array of simple objects. :param cls: The class that owns this field :param props: SQLAlchemy Mapper properties :param subname: Field name :param arrser_cust: Array serializer, ie the class itself inside the Array object :param storage: The storage configuration object passed to the store_as """ table_name = cls.Attributes.table_name metadata = cls.Attributes.sqla_metadata # get left (fk) column info _gen_col = _get_col_o2m(cls, storage.left, ondelete=storage.fk_left_ondelete, onupdate=storage.fk_left_onupdate, deferrable=storage.fk_left_deferrable, initially=storage.fk_left_initially) col_info = next(_gen_col) # gets the column name # FIXME: Add support for multi-column primary keys. storage.left, child_left_col_type = col_info[0] child_left_col_name = storage.left # get right(data) column info child_right_col_type = _get_sqlalchemy_type(arrser_cust) child_right_col_name = storage.right # this is the data column if child_right_col_name is None: child_right_col_name = subname # get table name child_table_name = arrser_cust.Attributes.table_name if child_table_name is None: child_table_name = '_'.join([table_name, subname]) if child_table_name in metadata.tables: child_t = metadata.tables[child_table_name] # if we have the table, make sure have the right column (data column) assert child_right_col_type.__class__ is \ child_t.c[child_right_col_name].type.__class__, "%s.%s: %r != %r" % \ (cls, child_right_col_name, child_right_col_type.__class__, child_t.c[child_right_col_name].type.__class__) if child_left_col_name in child_t.c: assert child_left_col_type is \ child_t.c[child_left_col_name].type.__class__, "%r != %r" % \ (child_left_col_type, child_t.c[child_left_col_name].type.__class__) else: # Table exists but our own foreign key doesn't. child_left_col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_left_col) child_t.append_column(child_left_col) else: # table does not exist, generate table child_right_col = Column(child_right_col_name, child_right_col_type) _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_right_col) child_left_col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=child_left_col) child_t = Table(child_table_name , metadata, Column('id', sqlalchemy.Integer, primary_key=True), child_left_col, child_right_col, ) _gen_index_info(child_t, child_right_col, child_right_col_name, arrser_cust) # generate temporary class for association proxy cls_name = ''.join(x.capitalize() or '_' for x in child_table_name.split('_')) # generates camelcase class name. def _i(self, *args): setattr(self, child_right_col_name, args[0]) cls_ = type("_" + cls_name, (object,), {'__init__': _i}) mapper(cls_, child_t) props["_" + subname] = relationship(cls_) # generate association proxy setattr(cls, subname, association_proxy("_" + subname, child_right_col_name)) def _gen_array_o2m(cls, props, subname, arrser, arrser_cust, storage): _gen_col = _get_col_o2m(cls, storage.right, ondelete=storage.fk_right_ondelete, onupdate=storage.fk_right_onupdate, deferrable=storage.fk_right_deferrable, initially=storage.fk_right_initially) col_info = next(_gen_col) # gets the column name storage.right, col_type = col_info[0] # FIXME: Add support for multi-column primary keys. assert storage.left is None, \ "'left' is ignored in one-to-many relationships " \ "with complex types (because they already have a " \ "table). You probably meant to use 'right'." child_t = arrser.__table__ if storage.right in child_t.c: # TODO: This branch MUST be tested. new_col_type = child_t.c[storage.right].type.__class__ assert col_type is child_t.c[storage.right].type.__class__, \ "Existing column type %r disagrees with new column type %r" % \ (col_type, new_col_type) # if the column is already there, the decision about whether # it should be in child's mapper or not should also have been # made. # # so, not adding the child column to to child mapper # here. col = child_t.c[storage.right] else: col = next(_gen_col) _sp_attrs_to_sqla_constraints(cls, arrser_cust, col=col) child_t.append_column(col) arrser.__mapper__.add_property(col.name, col) rel_kwargs = dict( lazy=storage.lazy, backref=storage.backref, cascade=storage.cascade, order_by=storage.order_by, foreign_keys=[col], back_populates=storage.back_populates, ) if storage.single_parent is not None: rel_kwargs['single_parent'] = storage.single_parent props[subname] = relationship(arrser, **rel_kwargs) def _is_array(v): return v.Attributes.max_occurs > 1 or issubclass(v, Array) def _add_array_to_complex(cls, props, subname, subcls, storage): arrser_cust = subcls if issubclass(subcls, Array): arrser_cust, = subcls._type_info.values() arrser = arrser_cust if arrser_cust.__orig__ is not None: arrser = arrser_cust.__orig__ if storage.multi != False: # many to many _gen_array_m2m(cls, props, subname, arrser, storage) elif issubclass(arrser, SimpleModel): # one to many simple type _gen_array_simple(cls, props, subname, arrser_cust, storage) else: # one to many complex type _gen_array_o2m(cls, props, subname, arrser, arrser_cust, storage) def _add_simple_type_to_complex(cls, props, table, subname, subcls, storage, col_kwargs): # v has the Attribute values we need whereas real_v is what the # user instantiates (thus what sqlalchemy needs) if subcls.__orig__ is None: # vanilla class real_v = subcls else: # customized class real_v = subcls.__orig__ assert not getattr(storage, 'multi', False), \ 'Storing a single element-type using a relation table is pointless.' assert storage.right is None, \ "'right' is ignored in a one-to-one relationship" col = _get_col_o2o(cls, subname, subcls, storage.left, ondelete=storage.fk_left_ondelete, onupdate=storage.fk_left_onupdate, deferrable=storage.fk_left_deferrable, initially=storage.fk_left_initially) storage.left = col.name if col.name in table.c: col = table.c[col.name] if col_kwargs.get('nullable') is False: col.nullable = False else: table.append_column(col) rel_kwargs = dict( lazy=storage.lazy, backref=storage.backref, order_by=storage.order_by, back_populates=storage.back_populates, ) if storage.single_parent is not None: rel_kwargs['single_parent'] = storage.single_parent if real_v is (cls.__orig__ or cls): (pk_col_name, pk_col_type), = get_pk_columns(cls) rel_kwargs['remote_side'] = [table.c[pk_col_name]] rel = relationship(real_v, uselist=False, foreign_keys=[col], **rel_kwargs) _gen_index_info(table, col, subname, subcls) props[subname] = rel props[col.name] = col def _add_complex_type_as_table(cls, props, table, subname, subcls, storage, col_args, col_kwargs): # add one to many relation if _is_array(subcls): _add_array_to_complex(cls, props, subname, subcls, storage) # add one to one relation else: _add_simple_type_to_complex(cls, props, table, subname, subcls, storage, col_kwargs) def _add_complex_type_as_xml(cls, props, table, subname, subcls, storage, col_args, col_kwargs): if 'name' in col_kwargs: colname = col_kwargs.pop('name') else: colname = subname if colname in table.c: col = table.c[colname] else: t = PGObjectXml(subcls, storage.root_tag, storage.no_ns, storage.pretty_print) col = Column(colname, t, **col_kwargs) props[subname] = col if not subname in table.c: table.append_column(col) def _add_complex_type_as_json(cls, props, table, subname, subcls, storage, col_args, col_kwargs, dbt): if 'name' in col_kwargs: colname = col_kwargs.pop('name') else: colname = subname if colname in table.c: col = table.c[colname] else: t = PGObjectJson(subcls, ignore_wrappers=storage.ignore_wrappers, complex_as=storage.complex_as, dbt=dbt) col = Column(colname, t, **col_kwargs) props[subname] = col if not subname in table.c: table.append_column(col) def _add_complex_type(cls, props, table, subname, subcls): if issubclass(subcls, File): return _add_file_type(cls, props, table, subname, subcls) storage = getattr(subcls.Attributes, 'store_as', None) col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) if isinstance(storage, c_table): return _add_complex_type_as_table(cls, props, table, subname, subcls, storage, col_args, col_kwargs) if isinstance(storage, c_xml): return _add_complex_type_as_xml(cls, props, table, subname, subcls, storage, col_args, col_kwargs) if isinstance(storage, c_json): return _add_complex_type_as_json(cls, props, table, subname, subcls, storage, col_args, col_kwargs, 'json') if isinstance(storage, c_jsonb): return _add_complex_type_as_json(cls, props, table, subname, subcls, storage, col_args, col_kwargs, 'jsonb') if isinstance(storage, c_msgpack): raise NotImplementedError(c_msgpack) if storage is None: return raise ValueError(storage) def _convert_fake_table(cls, table): metadata = cls.Attributes.sqla_metadata table_name = cls.Attributes.table_name _table = table table_args, table_kwargs = sanitize_args(cls.Attributes.sqla_table_args) table = Table(table_name, metadata, *(tuple(table.columns) + table_args), **table_kwargs) for index_args, index_kwargs in _table.indexes: Index(*index_args, **index_kwargs) return table def _gen_mapper(cls, props, table, cls_bases): """Generate SQLAlchemy mapper from Spyne definition data. :param cls: La Class. :param props: Dict of properties for SQLAlchemt'y Mapper call. :param table: A Table instance. Not a `_FakeTable` or anything. :param cls_bases: Sequence of class bases. """ inheritance, base_class, base_mapper, inc = _check_inheritance(cls, cls_bases) mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) _props = mapper_kwargs.get('properties', None) if _props is None: mapper_kwargs['properties'] = props else: props.update(_props) mapper_kwargs['properties'] = props po = mapper_kwargs.get('polymorphic_on', None) if po is not None: if not isinstance(po, Column): mapper_kwargs['polymorphic_on'] = table.c[po] else: logger.warning("Deleted invalid 'polymorphic_on' value %r for %r.", po, cls) del mapper_kwargs['polymorphic_on'] if base_mapper is not None: mapper_kwargs['inherits'] = base_mapper if inheritance is not _SINGLE: mapper_args = (table,) + mapper_args cls_mapper = mapper(cls, *mapper_args, **mapper_kwargs) def on_load(target, context): d = target.__dict__ for k, v in cls.get_flat_type_info(cls).items(): if not k in d: if isclass(v) and issubclass(v, ComplexModelBase): pass else: d[k] = None event.listen(cls, 'load', on_load) return cls_mapper def _add_file_type(cls, props, table, subname, subcls): storage = getattr(subcls.Attributes, 'store_as', None) col_args, col_kwargs = sanitize_args(subcls.Attributes.sqla_column_args) _sp_attrs_to_sqla_constraints(cls, subcls, col_kwargs) if isinstance(storage, HybridFileStore): if subname in table.c: col = table.c[subname] else: assert isabs(storage.store) #FIXME: Add support for storage markers from spyne.model.complex if storage.db_format == 'json': t = PGFileJson(storage.store, storage.type) elif storage.db_format == 'jsonb': t = PGFileJson(storage.store, storage.type, dbt='jsonb') else: raise NotImplementedError(storage.db_format) col = Column(subname, t, **col_kwargs) props[subname] = col if not subname in table.c: table.append_column(col) else: raise NotImplementedError(storage) def add_column(cls, subname, subcls): """Add field to the given Spyne object also mapped as a SQLAlchemy object to a SQLAlchemy table :param cls: The class to add the column to. :param subname: The column name :param subcls: The column type, a ModelBase subclass. """ table = cls.__table__ mapper_props = {} # Add to table sqla_type = _get_sqlalchemy_type(subcls) if sqla_type is None: # complex model _add_complex_type(cls, mapper_props, table, subname, subcls) else: _add_simple_type(cls, mapper_props, table, subname, subcls, sqla_type) # Add to mapper sqla_mapper = cls.Attributes.sqla_mapper for subname, subcls in mapper_props.items(): if not sqla_mapper.has_property(subname): sqla_mapper.add_property(subname, subcls) def _parent_mapper_has_property(cls, cls_bases, k): if len(cls_bases) == 0 and cls.__orig__ is cls_bases[0]: return False for b in cls_bases: if not hasattr(b, 'Attributes'): continue mapper = b.Attributes.sqla_mapper if mapper is not None and mapper.has_property(k): # print(" Skipping mapping field", "%s.%s" % (cls.__name__, k), # "because parent mapper from", b.__name__, "already has it") return True # print("NOT skipping mapping field", "%s.%s" % (cls.__name__, k)) return False def gen_sqla_info(cls, cls_bases=()): """Return SQLAlchemy table object corresponding to the passed Spyne object. Also maps given class to the returned table. """ table = _check_table(cls) mapper_props = {} ancestors = cls.ancestors() if len(ancestors) > 0: anc_mapper = ancestors[0].Attributes.sqla_mapper if anc_mapper is None: # no mapper in parent, use all fields fields = cls.get_flat_type_info(cls).items() elif anc_mapper.concrete: # there is mapper in parent and it's concrete, so use all fields fields = cls.get_flat_type_info(cls).items() else: # there is a mapper in parent and it's not concrete, so parent # columns are already mapped, so use only own fields. fields = cls._type_info.items() else: # when no parents, use all fields anyway. assert set(cls._type_info.items()) == \ set(cls.get_flat_type_info(cls).items()) fields = cls.get_flat_type_info(cls).items() for k, v in fields: if _parent_mapper_has_property(cls, cls_bases, k): continue t = _get_sqlalchemy_type(v) if t is None: # complex model p = getattr(v.Attributes, 'store_as', None) if p is None: logger.debug("Skipping %s.%s.%s: %r, store_as: %r" % ( cls.get_namespace(), cls.get_type_name(), k, v, p)) else: _add_complex_type(cls, mapper_props, table, k, v) else: _add_simple_type(cls, mapper_props, table, k, v, t) if isinstance(table, _FakeTable): table = _convert_fake_table(cls, table) cls_mapper = _gen_mapper(cls, mapper_props, table, cls_bases) cls.__tablename__ = cls.Attributes.table_name cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper cls.Attributes.sqla_table = cls.__table__ = table return table def _get_spyne_type(v): """Map sqlalchemy types to spyne types.""" cust = {} if v.primary_key: cust['primary_key'] = True if not v.nullable: cust['nullable'] = False cust['min_occurs'] = 1 if isinstance(v.type, sqlalchemy.Enum): if v.type.convert_unicode: return Unicode(values=v.type.enums, **cust) else: cust['type_name'] = v.type.name return Enum(*v.type.enums, **cust) if isinstance(v.type, (sqlalchemy.UnicodeText, sqlalchemy.Text)): return Unicode(**cust) if isinstance(v.type, (sqlalchemy.Unicode, sqlalchemy.String, sqlalchemy.VARCHAR)): return Unicode(v.type.length, **cust) if isinstance(v.type, sqlalchemy.Numeric): return Decimal(v.type.precision, v.type.scale, **cust) if isinstance(v.type, PGXml): if len(cust) > 0: return AnyXml(**cust) else: return AnyXml if isinstance(v.type, PGHtml): if len(cust) > 0: return AnyHtml(**cust) else: return AnyHtml if type(v.type) in _sq2sp_type_map: retval = _sq2sp_type_map[type(v.type)] if len(cust) > 0: return retval.customize(**cust) else: return retval if isinstance(v.type, (PGObjectJson, PGObjectXml)): retval = v.type.cls if len(cust) > 0: return retval.customize(**cust) else: return retval if isinstance(v.type, PGFileJson): retval = v.FileData if len(cust) > 0: return v.FileData.customize(**cust) else: return retval raise Exception("Spyne type was not found. Probably _sq2sp_type_map " "needs a new entry. %r" % v) def gen_spyne_info(cls): table = cls.Attributes.sqla_table _type_info = cls._type_info mapper_args, mapper_kwargs = sanitize_args(cls.Attributes.sqla_mapper_args) if len(_type_info) == 0: for c in table.c: _type_info[c.name] = _get_spyne_type(c) else: mapper_kwargs['include_properties'] = _type_info.keys() # Map the table to the object cls_mapper = mapper(cls, table, *mapper_args, **mapper_kwargs) cls.Attributes.table_name = cls.__tablename__ = table.name cls.Attributes.sqla_mapper = cls.__mapper__ = cls_mapper def get_pk_columns(cls): """Return primary key fields of a Spyne object.""" retval = [] for k, v in cls.get_flat_type_info(cls).items(): if v.Attributes.sqla_column_args is not None and \ v.Attributes.sqla_column_args[-1].get('primary_key', False): retval.append((k, v)) return tuple(retval) if len(retval) > 0 else None spyne-spyne-2.14.0/spyne/store/relational/document.py000066400000000000000000000301011417664205300227070ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) import os import json import shutil import sqlalchemy.dialects from uuid import uuid1 from mmap import mmap, ACCESS_READ from contextlib import closing from os.path import join, abspath, dirname, basename, isfile try: from lxml import etree from lxml import html from spyne.util.xml import get_object_as_xml, get_xml_as_object except ImportError as _import_error: etree = None html = None _local_import_error = _import_error def get_object_as_xml(*_, **__): raise _local_import_error def get_xml_as_object(*_, **__): raise _local_import_error from sqlalchemy.sql.type_api import UserDefinedType from spyne import ValidationError from spyne.model.relational import FileData from spyne.util import six from spyne.util.six import binary_type, text_type, BytesIO, StringIO from spyne.util.fileproxy import SeekableFileProxy class PGXml(UserDefinedType): def __init__(self, pretty_print=False, xml_declaration=False, encoding='UTF-8'): super(PGXml, self).__init__() self.xml_declaration = xml_declaration self.pretty_print = pretty_print self.encoding = encoding def get_col_spec(self, **_): return "xml" def bind_processor(self, dialect): def process(value): if value is None or \ isinstance(value, (six.text_type, six.binary_type)): return value if six.PY2: return etree.tostring(value, pretty_print=self.pretty_print, encoding=self.encoding, xml_declaration=False) return etree.tostring(value, pretty_print=self.pretty_print, encoding="unicode", xml_declaration=False) return process def result_processor(self, dialect, col_type): def process(value): if value is not None: return etree.fromstring(value) else: return value return process sqlalchemy.dialects.postgresql.base.ischema_names['xml'] = PGXml class PGHtml(UserDefinedType): def __init__(self, pretty_print=False, encoding='UTF-8'): super(PGHtml, self).__init__() self.pretty_print = pretty_print self.encoding = encoding def get_col_spec(self, **_): return "text" def bind_processor(self, dialect): def process(value): if isinstance(value, (six.text_type, six.binary_type)) \ or value is None: return value else: return html.tostring(value, pretty_print=self.pretty_print, encoding=self.encoding) return process def result_processor(self, dialect, col_type): def process(value): if value is not None and len(value) > 0: return html.fromstring(value) else: return None return process class PGJson(UserDefinedType): def __init__(self, encoding='UTF-8'): self.encoding = encoding def get_col_spec(self, **_): return "json" def bind_processor(self, dialect): def process(value): if isinstance(value, (text_type, binary_type)) or value is None: return value else: if six.PY2: return json.dumps(value, encoding=self.encoding) else: return json.dumps(value) return process def result_processor(self, dialect, col_type): def process(value): if isinstance(value, (text_type, binary_type)): return json.loads(value) else: return value return process sqlalchemy.dialects.postgresql.base.ischema_names['json'] = PGJson class PGJsonB(PGJson): def get_col_spec(self, **_): return "jsonb" sqlalchemy.dialects.postgresql.base.ischema_names['jsonb'] = PGJsonB class PGObjectXml(UserDefinedType): def __init__(self, cls, root_tag_name=None, no_namespace=False, pretty_print=False): self.cls = cls self.root_tag_name = root_tag_name self.no_namespace = no_namespace self.pretty_print = pretty_print def get_col_spec(self, **_): return "xml" def bind_processor(self, dialect): def process(value): if value is not None: return etree.tostring(get_object_as_xml(value, self.cls, self.root_tag_name, self.no_namespace), encoding='utf8', pretty_print=self.pretty_print, xml_declaration=False) return process def result_processor(self, dialect, col_type): def process(value): if value is not None: return get_xml_as_object(etree.fromstring(value), self.cls) return process class PGObjectJson(UserDefinedType): def __init__(self, cls, ignore_wrappers=True, complex_as=dict, dbt='json', encoding='utf8'): self.cls = cls self.ignore_wrappers = ignore_wrappers self.complex_as = complex_as self.dbt = dbt self.encoding = encoding from spyne.util.dictdoc import get_dict_as_object from spyne.util.dictdoc import get_object_as_json self.get_object_as_json = get_object_as_json self.get_dict_as_object = get_dict_as_object def get_col_spec(self, **_): return self.dbt def bind_processor(self, dialect): def process(value): if value is not None: try: return self.get_object_as_json(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, ).decode(self.encoding) except Exception as e: logger.debug("Failed to serialize %r to json: %r", value, e) raise return process def result_processor(self, dialect, col_type): from spyne.util.dictdoc import JsonDocument def process(value): if value is None: return None if isinstance(value, six.binary_type): value = value.decode(self.encoding) if isinstance(value, six.text_type): return self.get_dict_as_object(json.loads(value), self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, protocol=JsonDocument, ) return self.get_dict_as_object(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, protocol=JsonDocument, ) return process class PGFileJson(PGObjectJson): def __init__(self, store, type=None, dbt='json'): if type is None: type = FileData super(PGFileJson, self).__init__(type, ignore_wrappers=True, complex_as=list, dbt=dbt) self.store = store def bind_processor(self, dialect): def process(value): if value is not None: if value.data is not None: value.path = uuid1().hex fp = join(self.store, value.path) if not abspath(fp).startswith(self.store): raise ValidationError(value.path, "Path %r contains " "relative path operators (e.g. '..')") with open(fp, 'wb') as file: for d in value.data: file.write(d) elif value.handle is not None: value.path = uuid1().hex fp = join(self.store, value.path) if not abspath(fp).startswith(self.store): raise ValidationError(value.path, "Path %r contains " "relative path operators (e.g. '..')") if isinstance(value.handle, (StringIO, BytesIO)): with open(fp, 'wb') as out_file: out_file.write(value.handle.getvalue()) else: with closing(mmap(value.handle.fileno(), 0, access=ACCESS_READ)) as data: with open(fp, 'wb') as out_file: out_file.write(data) elif value.path is not None: in_file_path = value.path if not isfile(in_file_path): logger.error("File path in %r not found" % value) if dirname(abspath(in_file_path)) != self.store: dest = join(self.store, uuid1().get_hex()) if value.move: shutil.move(in_file_path, dest) logger.debug("move '%s' => '%s'", in_file_path, dest) else: shutil.copy(in_file_path, dest) logger.debug("copy '%s' => '%s'", in_file_path, dest) value.path = basename(dest) value.abspath = dest else: raise ValueError("Invalid file object passed in. All of " ".data, .handle and .path are None.") value.store = self.store value.abspath = join(self.store, value.path) return self.get_object_as_json(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as, ) return process def result_processor(self, dialect, col_type): def process(value): if value is None: return None if isinstance(value, six.text_type): value = json.loads(value) elif isinstance(value, six.binary_type): value = json.loads(value.decode('utf8')) retval = self.get_dict_as_object(value, self.cls, ignore_wrappers=self.ignore_wrappers, complex_as=self.complex_as) retval.store = self.store retval.abspath = path = join(self.store, retval.path) retval.handle = None retval.data = [b''] if not os.access(path, os.R_OK): import traceback traceback.print_stack() logger.error("File '%s' is not readable", path) return retval h = retval.handle = SeekableFileProxy(open(path, 'rb')) if os.fstat(retval.handle.fileno()).st_size > 0: h.mmap = mmap(h.fileno(), 0, access=ACCESS_READ) retval.data = (h.mmap,) # FIXME: Where do we close this mmap? return retval return process spyne-spyne-2.14.0/spyne/store/relational/override.py000066400000000000000000000045501417664205300227210ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from sqlalchemy.ext.compiler import compiles from sqlalchemy.dialects.postgresql import INET from spyne.store.relational import PGXml, PGJson, PGHtml, PGJsonB, \ PGObjectJson, PGFileJson @compiles(PGXml) def compile_xml(type_, compiler, **kw): return "xml" @compiles(PGHtml) def compile_html(type_, compiler, **kw): return "text" @compiles(PGJson) def compile_json(type_, compiler, **kw): return type_.get_col_spec() @compiles(PGJsonB) def compile_jsonb(type_, compiler, **kw): return type_.get_col_spec() @compiles(PGObjectJson) def compile_ojson(type_, compiler, **kw): return type_.get_col_spec() @compiles(PGFileJson) def compile_fjson(type_, compiler, **kw): return type_.get_col_spec() @compiles(INET) def compile_inet(type_, compiler, **kw): return "inet" @compiles(PGXml, "firebird") def compile_xml_firebird(type_, compiler, **kw): return "blob" @compiles(PGHtml, "firebird") def compile_html_firebird(type_, compiler, **kw): return "blob" @compiles(PGJson, "firebird") def compile_json_firebird(type_, compiler, **kw): return "blob" @compiles(PGJsonB, "firebird") def compile_jsonb_firebird(type_, compiler, **kw): return "blob" @compiles(PGObjectJson, "firebird") def compile_ojson_firebird(type_, compiler, **kw): return "blob" @compiles(PGFileJson, "firebird") def compile_fjson_firebird(type_, compiler, **kw): return "blob" @compiles(INET, "firebird") def compile_inet_firebird(type_, compiler, **kw): # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/netinet_in.h.html # INET6_ADDRSTRLEN return "varchar(45)" spyne-spyne-2.14.0/spyne/store/relational/simple.py000066400000000000000000000052721417664205300223750ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from sqlalchemy import sql from sqlalchemy.ext.compiler import compiles from sqlalchemy.dialects.postgresql.base import ischema_names, PGTypeCompiler from sqlalchemy.dialects.postgresql import ARRAY as PGArray, UUID as PGUuid from sqlalchemy.sql.sqltypes import Concatenable from sqlalchemy.sql.type_api import UserDefinedType @compiles(PGUuid, "sqlite") def compile_uuid_sqlite(type_, compiler, **kw): return "BLOB" class PGLTree(Concatenable, UserDefinedType): """Postgresql `ltree` type.""" class Comparator(Concatenable.Comparator): def ancestor_of(self, other): if isinstance(other, list): return self.op('@>')(sql.cast(other, PGArray(PGLTree))) else: return self.op('@>')(other) def descendant_of(self, other): if isinstance(other, list): return self.op('<@')(sql.cast(other, PGArray(PGLTree))) else: return self.op('<@')(other) def lquery(self, other): if isinstance(other, list): return self.op('?')(sql.cast(other, PGArray(PGLQuery))) else: return self.op('~')(other) def ltxtquery(self, other): return self.op('@')(other) comparator_factory = Comparator __visit_name__ = 'LTREE' class PGLQuery(UserDefinedType): """Postresql `lquery` type.""" __visit_name__ = 'LQUERY' class PGLTxtQuery(UserDefinedType): """Postresql `ltxtquery` type.""" __visit_name__ = 'LTXTQUERY' ischema_names['ltree'] = PGLTree ischema_names['lquery'] = PGLQuery ischema_names['ltxtquery'] = PGLTxtQuery def visit_LTREE(self, type_, **kw): return 'LTREE' def visit_LQUERY(self, type_, **kw): return 'LQUERY' def visit_LTXTQUERY(self, type_, **kw): return 'LTXTQUERY' PGTypeCompiler.visit_LTREE = visit_LTREE PGTypeCompiler.visit_LQUERY = visit_LQUERY PGTypeCompiler.visit_LTXTQUERY = visit_LTXTQUERY spyne-spyne-2.14.0/spyne/store/relational/spatial.py000066400000000000000000000052311417664205300225340ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from sqlalchemy import sql from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql.type_api import UserDefinedType class PGGeometry(UserDefinedType): """Geometry type for Postgis 2""" class PlainWkt: pass class PlainWkb: pass def __init__(self, geometry_type='GEOMETRY', srid=4326, dimension=2, format='wkt'): self.geometry_type = geometry_type.upper() self.name = 'geometry' self.srid = int(srid) self.dimension = dimension self.format = format if self.format == 'wkt': self.format = PGGeometry.PlainWkt elif self.format == 'wkb': self.format = PGGeometry.PlainWkb def get_col_spec(self): return '%s(%s,%d)' % (self.name, self.geometry_type, self.srid) def column_expression(self, col): if self.format is PGGeometry.PlainWkb: return sql.func.ST_AsBinary(col, type_=self) if self.format is PGGeometry.PlainWkt: return sql.func.ST_AsText(col, type_=self) def result_processor(self, dialect, coltype): if self.format is PGGeometry.PlainWkt: def process(value): if value is not None: return value if self.format is PGGeometry.PlainWkb: def process(value): if value is not None: return sql.func.ST_AsBinary(value, self.srid) return process def bind_expression(self, bindvalue): if self.format is PGGeometry.PlainWkt: return sql.func.ST_GeomFromText(bindvalue, self.srid) Geometry = PGGeometry @compiles(PGGeometry) def compile_geometry(type_, compiler, **kw): return '%s(%s,%d)' % (type_.name, type_.geometry_type, type_.srid) @compiles(PGGeometry, "sqlite") def compile_geometry_sqlite(type_, compiler, **kw): return "BLOB" spyne-spyne-2.14.0/spyne/store/relational/util.py000066400000000000000000000216301417664205300220550ustar00rootroot00000000000000# # retrieved from https://github.com/kvesteri/sqlalchemy-utils # commit 99e1ea0eb288bc50ddb4a4aed5c50772d915ca73 # # Copyright (c) 2012, Konsta Vesterinen # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * The names of the contributors may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import os import cProfile from copy import copy from sqlalchemy.engine import Dialect, create_engine from sqlalchemy.engine.url import make_url from sqlalchemy.exc import OperationalError, ProgrammingError from sqlalchemy.orm import object_session from sqlalchemy.orm.exc import UnmappedInstanceError def get_bind(obj): """ Return the bind for given SQLAlchemy Engine / Connection / declarative model object. :param obj: SQLAlchemy Engine / Connection / declarative model object :: from sqlalchemy_utils import get_bind get_bind(session) # Connection object get_bind(user) """ if hasattr(obj, 'bind'): conn = obj.bind else: try: conn = object_session(obj).bind except UnmappedInstanceError: conn = obj if not hasattr(conn, 'execute'): raise TypeError( 'This method accepts only Session, Engine, Connection and ' 'declarative model objects.' ) return conn def quote(mixed, ident): """ Conditionally quote an identifier. :: from sqlalchemy_utils import quote engine = create_engine('sqlite:///:memory:') quote(engine, 'order') # '"order"' quote(engine, 'some_other_identifier') # 'some_other_identifier' :param mixed: SQLAlchemy Session / Connection / Engine / Dialect object. :param ident: identifier to conditionally quote """ if isinstance(mixed, Dialect): dialect = mixed else: dialect = get_bind(mixed).dialect return dialect.preparer(dialect).quote(ident) def database_exists(url): """Check if a database exists. :param url: A SQLAlchemy engine URL. Performs backend-specific testing to quickly determine if a database exists on the server. :: database_exists('postgresql://postgres@localhost/name') #=> False create_database('postgresql://postgres@localhost/name') database_exists('postgresql://postgres@localhost/name') #=> True Supports checking against a constructed URL as well. :: engine = create_engine('postgresql://postgres@localhost/name') database_exists(engine.url) #=> False create_database(engine.url) database_exists(engine.url) #=> True """ url = copy(make_url(url)) database = url.database if url.drivername.startswith('postgres'): url.database = 'postgres' elif not url.drivername.startswith('sqlite'): url.database = None engine = create_engine(url) if engine.dialect.name == 'postgresql': text = "SELECT 1 FROM pg_database WHERE datname='%s'" % database return bool(engine.execute(text).scalar()) elif engine.dialect.name == 'mysql': text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA " "WHERE SCHEMA_NAME = '%s'" % database) return bool(engine.execute(text).scalar()) elif engine.dialect.name == 'sqlite': return database == ':memory:' or os.path.exists(database) else: text = 'SELECT 1' try: url.database = database engine = create_engine(url) engine.execute(text) return True except (ProgrammingError, OperationalError): return False def create_database(url, encoding='utf8', psql_template='template1'): """Issue the appropriate CREATE DATABASE statement. :param url: A SQLAlchemy engine URL. :param encoding: The encoding to create the database as. :param psql_template: The name of the template from which to create the new database, only supported by PostgreSQL driver. As per Postgresql docs, defaults to "template1". To create a database, you can pass a simple URL that would have been passed to ``create_engine``. :: create_database('postgresql://postgres@localhost/name') You may also pass the url from an existing engine. :: create_database(engine.url) Has full support for mysql, postgres, and sqlite. In theory, other database engines should be supported. """ url = copy(make_url(url)) database = url.database if url.drivername.startswith('postgres'): url.database = 'postgres' elif not url.drivername.startswith('sqlite'): url.database = None engine = create_engine(url) if engine.dialect.name == 'postgresql': if engine.driver == 'psycopg2': from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT engine.raw_connection().set_isolation_level( ISOLATION_LEVEL_AUTOCOMMIT ) text = "CREATE DATABASE {0} ENCODING '{1}' TEMPLATE {2}".format( quote(engine, database), encoding, quote(engine, psql_template) ) engine.execute(text) elif engine.dialect.name == 'mysql': text = "CREATE DATABASE {0} CHARACTER SET = '{1}'".format( quote(engine, database), encoding ) engine.execute(text) elif engine.dialect.name == 'sqlite' and database != ':memory:': open(database, 'w').close() else: text = 'CREATE DATABASE {0}'.format(quote(engine, database)) engine.execute(text) def drop_database(url): """Issue the appropriate DROP DATABASE statement. :param url: A SQLAlchemy engine URL. Works similar to the :ref:`create_database` method in that both url text and a constructed url are accepted. :: drop_database('postgresql://postgres@localhost/name') drop_database(engine.url) """ url = copy(make_url(url)) database = url.database if url.drivername.startswith('postgresql'): url.database = 'template1' elif not url.drivername.startswith('sqlite'): url.database = None engine = create_engine(url) if engine.dialect.name == 'sqlite' and url.database != ':memory:': os.remove(url.database) elif engine.dialect.name == 'postgresql' and engine.driver == 'psycopg2': from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT engine.raw_connection().set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # Disconnect all users from the database we are dropping. version = list( map( int, engine.execute('SHOW server_version').first()[0].split('.') ) ) pid_column = ( 'pid' if (version[0] >= 9 and version[1] >= 2) else 'procpid' ) text = ''' SELECT pg_terminate_backend(pg_stat_activity.%(pid_column)s) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%(database)s' AND %(pid_column)s <> pg_backend_pid(); ''' % {'pid_column': pid_column, 'database': database} engine.execute(text) # Drop the database. text = 'DROP DATABASE {0}'.format(quote(engine, database)) engine.execute(text) else: text = 'DROP DATABASE {0}'.format(quote(engine, database)) engine.execute(text) # https://zapier.com/engineering/profiling-python-boss/ def do_cprofile(func): def profiled_func(*args, **kwargs): profile = cProfile.Profile() try: profile.enable() result = func(*args, **kwargs) profile.disable() return result finally: profile.print_stats(sort='time') return profiled_func spyne-spyne-2.14.0/spyne/test/000077500000000000000000000000001417664205300162155ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/README.rst000066400000000000000000000114311417664205300177040ustar00rootroot00000000000000 ********************* Running Tests Locally ********************* While the test coverage for Spyne is not too bad, we always accept new tests that cover new use-cases. Please consider contributing tests even if your use-case is working fine! Given the nature of open-source projects, Spyne may shift focus or change maintainers in the future. This can result in patches which may cause incompatibilities with your existing code base. The only way to detect such corner cases is to have a great test suite. Spyne's master repository is already integrated with travis-ci.org. Head over to http://travis-ci.org/arskom/spyne to see the test results for yourself. As the necessary configuration is already done, it's very simple to integrate your own fork of Spyne with travis-ci.org, which should come in handy even if you don't plan to be a long-time contributor to Spyne. Just sign in with your Github account and follow instructions. If you want to run the tests locally, first you have to install all dependencies (you may want to use virtualenv for that). :: pip install -r requirements/test_requirements.txt and after all dependencies are installed you can run tests using the canonical test command :: python setup.py test If you want to run only tests that are supposed to pass under Python 3, run: :: python setup.py test_python3 We use tox as well, but only for django tests. So if you just want to run Spyne <=> Django interop tests with all combinations of supported CPython and Django versions, run: :: tox The full list of environments that tox supports can be found inside ``setup.py``\. Spyne's generic test script does not run WS-I tests. Also see the related section below. If you don't want this or just want to run a specific test, `pytest `_ is a nice tool that lets you do just that: :: py.test -v --tb=short spyne/test/protocol/test_json.py You can run tests directly by executing them as well. This will use Python's builtin ``unittest`` package which is less polished, but just fine. :: spyne/test/protocol/test_json.py Note that just running ``py.test`` or similar powerful test-juggling software naively in the root directory of tests won't work. Spyne runs some interoperability tests by starting an actual daemon listening to a particular port and then making (or processing) real requests, so running all tests in one go is problematic. The rather specialized logic in setup.py for running tests is the result of these quirks. Patches are welcome! SOAP Interoperability Tests =========================== The interoperability servers require twisted.web. Python ------ Python interop tests currently use Spyne's own clients and suds (specifically suds-community fork). The suds test is the first thing we check and try not to break. Ruby ---- You need Ruby 1.8.x to run the ruby interop test against soap_http_basic. Unfortunately, the Ruby Soap client does not do proper handling of namespaces, so you'll need to turn off strict validation if you want to work with ruby clients. Ruby test module is very incomplete, implementing only two (echo_string and echo_integer) tests. We're looking for volunteers who'd like to work on increasing test coverage for other use cases. .Net ---- There isn't any .Net tests for Spyne. WS-I test compliance reportedly covers .Net use cases as well. Patches are welcome! Java ---- The WS-I test is written in Java. But unfortunately, it only focuses on Wsdl document and not the Soap functionality itself. We're looking for volunteers who'd like to work on writing Java interop tests for spyne. To run the Wsdl tests, you should first get wsi-interop-tools package from http://ws-i.org and unpack it next to test_wsi.py. Here are the relevant links: http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools http://www.ws-i.org/Testing/Tools/2005/06/WSI_Test_Java_Final_1.1.zip See also test_wsi.py for more info. Now run the soap_http_basic interop test server and run test_wsi.py. If all goes well, you should get a new wsi-report-spyne.xml file in the same directory. Here's the directory tree from a working setup: :: |-- README.rst |-- (...) |-- interop | |-- (...) | |-- test_wsi.py | `-- wsi-test-tools | |-- License.htm | |-- README.txt | `-- (...) `-- (...) *************************** Integrating with CI systems *************************** Spyne is already integrated with Jenkins and travis-ci.org. The travis configuration file is located in the root of the source repository, under its standard name: .travis.yml A script for running Spyne test suite inside Jenkins can also be found in the project root directory, under the name run_tests.sh. It's supposed to be used as a multi-configuration project. See the script header for more information. spyne-spyne-2.14.0/spyne/test/__init__.py000066400000000000000000000020351417664205300203260ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # class FakeApp(object): transport = 'transport' tns = 'tns' name = 'name' services = [] classes = () import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.util.appreg').setLevel(logging.INFO) from spyne.context import FakeContext spyne-spyne-2.14.0/spyne/test/interface/000077500000000000000000000000001417664205300201555ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interface/__init__.py000066400000000000000000000000001417664205300222540ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interface/test_interface.py000077500000000000000000000067351417664205300235440ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne import Application, Service, rpc from spyne.model import Array, ComplexModel, AnyXml, UnsignedLong, \ UnsignedInteger16, Integer, DateTime, Unicode from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 class TestInterface(unittest.TestCase): def test_imports(self): import logging logging.basicConfig(level=logging.DEBUG) class KeyValuePair(ComplexModel): __namespace__ = "1" key = Unicode value = Unicode class Something(ComplexModel): __namespace__ = "2" d = DateTime i = Integer class SomethingElse(ComplexModel): __namespace__ = "3" a = AnyXml b = UnsignedLong s = Something class BetterSomething(Something): __namespace__ = "4" k = UnsignedInteger16 class Service1(Service): @rpc(SomethingElse, _returns=Array(KeyValuePair)) def some_call(ctx, sth): pass class Service2(Service): @rpc(BetterSomething, _returns=Array(KeyValuePair)) def some_other_call(ctx, sth): pass application = Application([Service1, Service2], in_protocol=HttpRpc(), out_protocol=Soap11(), name='Service', tns='target_namespace' ) imports = application.interface.imports tns = application.interface.get_tns() smm = application.interface.service_method_map print(imports) assert imports[tns] == set(['1', '3', '4']) assert imports['3'] == set(['2']) assert imports['4'] == set(['2']) assert smm['{%s}some_call' % tns] assert smm['{%s}some_call' % tns][0].service_class == Service1 assert smm['{%s}some_call' % tns][0].function == Service1.some_call assert smm['{%s}some_other_call' % tns] assert smm['{%s}some_other_call' % tns][0].service_class == Service2 assert smm['{%s}some_other_call' % tns][0].function == Service2.some_other_call def test_custom_primitive_in_array(self): RequestStatus = Unicode(values=['new', 'processed'], zonta='bonta') class DataRequest(ComplexModel): status = Array(RequestStatus) class HelloWorldService(Service): @rpc(DataRequest) def some_call(ctx, dgrntcl): pass Application([HelloWorldService], 'spyne.examples.hello.soap', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) # test passes if instantiating Application doesn't fail if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interface/test_wsgi.py000077500000000000000000000056201417664205300225450ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.util import six from spyne.util.six import StringIO from spyne.protocol.soap.soap11 import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.application import Application from spyne.model.primitive import Unicode from spyne.decorator import rpc from spyne.const.xml import WSDL11 from spyne.service import Service def start_response(code, headers): print(code, headers) class Test(unittest.TestCase): def setUp(self): class SomeService(Service): @rpc(Unicode) def some_call(ctx, some_str): print(some_str) app = Application([SomeService], "some_tns", in_protocol=Soap11(), out_protocol=Soap11()) self.wsgi_app = WsgiApplication(app) def test_document_built(self): self.h = 0 def on_wsdl_document_built(doc): self.h += 1 self.wsgi_app.doc.wsdl11.event_manager.add_listener( "wsdl_document_built", on_wsdl_document_built) self.wsgi_app.doc.wsdl11.build_interface_document("http://some_url/") assert self.h == 1 def test_document_manipulation(self): def on_wsdl_document_built(doc): doc.root_elt.tag = 'ehe' self.wsgi_app.doc.wsdl11.event_manager.add_listener( "wsdl_document_built", on_wsdl_document_built) self.wsgi_app.doc.wsdl11.build_interface_document("http://some_url/") d = self.wsgi_app.doc.wsdl11.get_interface_document() from lxml import etree assert etree.fromstring(d).tag == 'ehe' def test_wsgi(self): retval = b''.join(self.wsgi_app({ 'PATH_INFO': '/', 'QUERY_STRING': 'wsdl', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '7000', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'wsgi.input': StringIO(), }, start_response)) from lxml import etree assert etree.fromstring(retval).tag == WSDL11('definitions') if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interface/test_xml_schema.py000077500000000000000000000450671417664205300237250ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging import unittest from pprint import pprint from lxml import etree from spyne import Application from spyne import rpc from spyne.const import xml as ns from spyne.const.xml import NS_XSD from spyne.model import ByteArray from spyne.model import ComplexModel from spyne.model import XmlAttribute from spyne.model import XmlData from spyne.model import AnyXml from spyne.model import Integer from spyne.model import Mandatory as M from spyne.model import Unicode from spyne.model import Uuid from spyne.model import Boolean from spyne.protocol.soap import Soap11, Soap12 from spyne.service import Service from spyne.util.xml import get_schema_documents from spyne.util.xml import parse_schema_element from spyne.util.xml import parse_schema_string from spyne.interface.xml_schema import XmlSchema from spyne.interface.xml_schema.genpy import CodeGenerator class TestXmlSchema(unittest.TestCase): def test_choice_tag(self): class SomeObject(ComplexModel): __namespace__ = "badass_ns" one = Integer(xml_choice_group="numbers") two = Integer(xml_choice_group="numbers") punk = Unicode class KickassService(Service): @rpc(_returns=SomeObject) def wooo(ctx): return SomeObject() Application([KickassService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) docs = get_schema_documents([SomeObject]) doc = docs['tns'] print(etree.tostring(doc, pretty_print=True)) assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' '/xs:sequence/xs:element[@name="punk"]', namespaces={'xs': NS_XSD})) > 0 assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' '/xs:sequence/xs:choice/xs:element[@name="one"]', namespaces={'xs': NS_XSD})) > 0 def test_customized_class_with_empty_subclass(self): class SummaryStatsOfDouble(ComplexModel): _type_info = [('Min', XmlAttribute(Integer, use='required')), ('Max', XmlAttribute(Integer, use='required')), ('Avg', XmlAttribute(Integer, use='required'))] class SummaryStats(SummaryStatsOfDouble): ''' this is an empty base class ''' class Payload(ComplexModel): _type_info = [('Stat1', SummaryStats.customize(nillable=False)), ('Stat2', SummaryStats), ('Stat3', SummaryStats), ('Dummy', Unicode)] class JackedUpService(Service): @rpc(_returns=Payload) def GetPayload(ctx): return Payload() Application([JackedUpService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. # see: https://github.com/arskom/spyne/issues/226 def test_namespaced_xml_attribute(self): class Release(ComplexModel): __namespace__ = "http://usefulinc.com/ns/doap#" _type_info = [ ('about', XmlAttribute(Unicode, ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), ] class Project(ComplexModel): __namespace__ = "http://usefulinc.com/ns/doap#" _type_info = [ ('about', XmlAttribute(Unicode, ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#")), ('release', Release.customize(max_occurs=float('inf'))), ] class RdfService(Service): @rpc(Unicode, Unicode, _returns=Project) def some_call(ctx, a, b): pass Application([RdfService], tns='spynepi', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. def test_customized_simple_type_in_xml_attribute(self): class Product(ComplexModel): __namespace__ = 'some_ns' id = XmlAttribute(Uuid) edition = Unicode class SomeService(Service): @rpc(Product, _returns=Product) def echo_product(ctx, product): logging.info('edition_id: %r', product.edition_id) return product Application([SomeService], tns='some_ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. def test_binary_encodings(self): class Product(ComplexModel): __namespace__ = 'some_ns' hex = ByteArray(encoding='hex') base64_1 = ByteArray(encoding='base64') base64_2 = ByteArray class SomeService(Service): @rpc(Product, _returns=Product) def echo_product(ctx, product): logging.info('edition_id: %r', product.edition_id) return product app = Application([SomeService], tns='some_ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) _ns = {'xs': NS_XSD} pref_xs = ns.PREFMAP[NS_XSD] xs = XmlSchema(app.interface) xs.build_interface_document() elt = xs.get_interface_document()['tns'].xpath( '//xs:complexType[@name="Product"]', namespaces=_ns)[0] assert elt.xpath('//xs:element[@name="base64_1"]/@type', namespaces=_ns)[0] == '%s:base64Binary' % pref_xs assert elt.xpath('//xs:element[@name="base64_2"]/@type', namespaces=_ns)[0] == '%s:base64Binary' % pref_xs assert elt.xpath('//xs:element[@name="hex"]/@type', namespaces=_ns)[0] == '%s:hexBinary' % pref_xs def test_multilevel_customized_simple_type(self): class ExampleService(Service): __tns__ = 'http://xml.company.com/ns/example/' @rpc(M(Uuid), _returns=Unicode) def say_my_uuid(ctx, uuid): return 'Your UUID: %s' % uuid Application([ExampleService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if no exceptions while building the schema, no problem. # see: http://stackoverflow.com/questions/16042132/cannot-use-mandatory-uuid-or-other-pattern-related-must-be-type-as-rpc-argumen def test_any_tag(self): logging.basicConfig(level=logging.DEBUG) class SomeType(ComplexModel): __namespace__ = "zo" anything = AnyXml(schema_tag='{%s}any' % NS_XSD, namespace='##other', process_contents='lax') docs = get_schema_documents([SomeType]) print(etree.tostring(docs['tns'], pretty_print=True)) _any = docs['tns'].xpath('//xsd:any', namespaces={'xsd': NS_XSD}) assert len(_any) == 1 assert _any[0].attrib['namespace'] == '##other' assert _any[0].attrib['processContents'] == 'lax' def _build_xml_data_test_schema(self, custom_root): tns = 'kickass.ns' class ProductEdition(ComplexModel): __namespace__ = tns id = XmlAttribute(Uuid) if custom_root: name = XmlData(Uuid) else: name = XmlData(Unicode) class Product(ComplexModel): __namespace__ = tns id = XmlAttribute(Uuid) edition = ProductEdition class ExampleService(Service): @rpc(Product, _returns=Product) def say_my_uuid(ctx, product): pass app = Application([ExampleService], tns='kickass.ns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) schema = XmlSchema(app.interface) schema.build_interface_document() schema.build_validation_schema() doc = schema.get_interface_document()['tns'] print(etree.tostring(doc, pretty_print=True)) return schema def test_xml_data_schema_doc(self): schema = self._build_xml_data_test_schema(custom_root=False) assert len(schema.get_interface_document()['tns'].xpath( '/xs:schema/xs:complexType[@name="ProductEdition"]' '/xs:simpleContent/xs:extension/xs:attribute[@name="id"]' ,namespaces={'xs': NS_XSD})) == 1 def _test_xml_data_validation(self): schema = self._build_xml_data_test_schema(custom_root=False) assert schema.validation_schema.validate(etree.fromstring(""" punk """)), schema.validation_schema.error_log.last_error def _test_xml_data_validation_custom_root(self): schema = self._build_xml_data_test_schema(custom_root=True) assert schema.validation_schema.validate(etree.fromstring(""" 00000000-0000-0000-0000-000000000002 """)), schema.validation_schema.error_log.last_error def test_subs(self): from lxml import etree from spyne.util.xml import get_schema_documents xpath = lambda o, x: o.xpath(x, namespaces={"xs": NS_XSD}) m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = Integer b = Integer(sub_name="bb") c = Integer(sub_ns="cc") d = Integer(sub_ns="dd", sub_name="dd") elt = get_schema_documents([C], "aa")['tns'] print(etree.tostring(elt, pretty_print=True)) seq, = xpath(elt, "xs:complexType/xs:sequence") assert len(seq) == 4 assert len(xpath(seq, 'xs:element[@name="a"]')) == 1 assert len(xpath(seq, 'xs:element[@name="bb"]')) == 1 # FIXME: this doesn't feel right. # check the spec to see whether it should it be prefixed. # #assert len(xpath(seq, 'xs:element[@name="{cc}c"]')) == 1 #assert len(xpath(seq, 'xs:element[@name="{dd}dd"]')) == 1 def test_mandatory(self): xpath = lambda o, x: o.xpath(x, namespaces={"xs": NS_XSD}) class C(ComplexModel): __namespace__ = "aa" foo = XmlAttribute(M(Unicode)) elt = get_schema_documents([C])['tns'] print(etree.tostring(elt, pretty_print=True)) foo, = xpath(elt, 'xs:complexType/xs:attribute[@name="foo"]') attrs = foo.attrib assert 'use' in attrs and attrs['use'] == 'required' def test_annotation(self): tns = 'some_ns' doc = "Some Doc" class SomeClass(ComplexModel): __namespace__ = tns some_attr = Unicode(doc=doc) schema = get_schema_documents([SomeClass], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) assert schema.xpath("//xs:documentation/text()", namespaces={'xs': NS_XSD}) == [doc] class TestParseOwnXmlSchema(unittest.TestCase): def test_simple(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = 'some_ns' id = Integer schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects[tns].types["SomeGuy"] assert NewGuy.get_type_name() == SomeGuy.get_type_name() assert NewGuy.get_namespace() == SomeGuy.get_namespace() assert dict(NewGuy._type_info) == dict(SomeGuy._type_info) def test_customized_unicode(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = Unicode(max_len=10, pattern="a", min_len=5, default="aa") schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].Attributes.max_len == 10 assert NewGuy._type_info['name'].Attributes.min_len == 5 assert NewGuy._type_info['name'].Attributes.pattern == "a" assert NewGuy._type_info['name'].Attributes.default == "aa" def test_boolean_default(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns bald = Boolean(default=True) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['bald'].Attributes.default == True def test_boolean_attribute_default(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns bald = XmlAttribute(Boolean(default=True)) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['bald'].Attributes.default == True def test_attribute(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = XmlAttribute(Unicode) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].type is Unicode def test_attribute_with_customized_type(self): tns = 'some_ns' class SomeGuy(ComplexModel): __namespace__ = tns name = XmlAttribute(Unicode(default="aa")) schema = get_schema_documents([SomeGuy], tns)['tns'] print(etree.tostring(schema, pretty_print=True)) objects = parse_schema_element(schema) pprint(objects[tns].types) NewGuy = objects['some_ns'].types["SomeGuy"] assert NewGuy._type_info['name'].type.__orig__ is Unicode assert NewGuy._type_info['name'].type.Attributes.default == "aa" def test_inherited_attribute(self): class DeviceEntity(ComplexModel): token = XmlAttribute(Unicode, use='required') class DigitalInput(DeviceEntity): IdleState = XmlAttribute(Unicode) class SomeService(Service): @rpc(_returns=DigitalInput, _body_style='bare') def GetDigitalInput(ctx): return DigitalInput() Application([SomeService], 'some_tns', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) def test_simple_type_explicit_customization(self): class Header(ComplexModel): test = Boolean(min_occurs=0, nillable=False) pw = Unicode.customize(min_occurs=0, nillable=False, min_len=6) class Params(ComplexModel): sendHeader = Header.customize(nillable=False, min_occurs=1) class DummyService(Service): @rpc(Params, _returns=Unicode) def loadServices(ctx, serviceParams): return '42' Application([DummyService], tns='dummy', name='DummyService', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11() ) # if instantiation doesn't fail, test is green. class TestParseForeignXmlSchema(unittest.TestCase): def test_simple_content(self): tns = 'some_ns' schema = """ """ objects = parse_schema_string(schema) pprint(objects[tns].types) NewGuy = objects[tns].types['SomeGuy'] ti = NewGuy._type_info pprint(dict(ti)) assert issubclass(ti['_data'], XmlData) assert ti['_data'].type is Unicode assert issubclass(ti['attr'], XmlAttribute) assert ti['attr'].type is Unicode class TestCodeGeneration(unittest.TestCase): def _get_schema(self, *args): schema_doc = get_schema_documents(args)['tns'] return parse_schema_element(schema_doc) def test_simple(self): ns = 'some_ns' class SomeObject(ComplexModel): __namespace__ = ns _type_info = [ ('i', Integer), ('s', Unicode), ] s = self._get_schema(SomeObject)[ns] code = CodeGenerator().genpy(ns, s) # FIXME: Properly parse it assert """class SomeObject(_ComplexBase): _type_info = [ ('i', Integer), ('s', Unicode), ]""" in code if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interface/wsdl/000077500000000000000000000000001417664205300211265ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interface/wsdl/__init__.py000066400000000000000000000050611417664205300232410ustar00rootroot00000000000000 # # spyne - Copyright (C) spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.application import Application from spyne.interface.wsdl import Wsdl11 from spyne.protocol.soap import Soap11 import spyne.const.xml as ns def build_app(service_list, tns, name): app = Application(service_list, tns, name=name, in_protocol=Soap11(), out_protocol=Soap11()) app.transport = 'http://schemas.xmlsoap.org/soap/http' return app class AppTestWrapper(): def __init__(self, application): self.url = 'http:/localhost:7789/wsdl' self.service_string = ns.WSDL11('service') self.port_string = ns.WSDL11('port') self.soap_binding_string = ns.WSDL11_SOAP('binding') self.operation_string = ns.WSDL11('operation') self.port_type_string = ns.WSDL11('portType') self.binding_string = ns.WSDL11('binding') self.app = application self.interface_doc = Wsdl11(self.app.interface) self.interface_doc.build_interface_document(self.url) self.wsdl = self.interface_doc.get_interface_document() def get_service_list(self): return self.interface_doc.root_elt.findall(self.service_string) def get_port_list(self, service): from lxml import etree print((etree.tostring(service, pretty_print=True))) return service.findall(self.port_string) def get_soap_bindings(self, binding): return binding.findall(self.soap_binding_string) def get_port_types(self): return self.interface_doc.root_elt.findall(self.port_type_string) def get_port_operations(self, port_type): return port_type.findall(self.operation_string) def get_bindings(self): return self.interface_doc.root_elt.findall(self.binding_string) def get_binding_operations(self, binding): return [o for o in binding.iterfind(self.operation_string)] spyne-spyne-2.14.0/spyne/test/interface/wsdl/defult_services.py000066400000000000000000000030131417664205300246630ustar00rootroot00000000000000 # # spyne - Copyright (C) spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.model.primitive import String from spyne.service import Service from spyne.decorator import rpc def TDefaultPortService(): class DefaultPortService(Service): @rpc(String, _returns=String) def echo_default_port_service(self, string): return string return DefaultPortService def TDefaultPortServiceMultipleMethods(): class DefaultPortServiceMultipleMethods(Service): @rpc(String, _returns=String) def echo_one(self, string): return string @rpc(String, _returns=String) def echo_two(self, string): return string @rpc(String, _returns=String) def echo_three(self, string): return string return DefaultPortServiceMultipleMethods spyne-spyne-2.14.0/spyne/test/interface/wsdl/port_service_services.py000066400000000000000000000074041417664205300261140ustar00rootroot00000000000000 # # spyne - Copyright (C) spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.model.primitive import String from spyne.service import Service from spyne.decorator import rpc def TS1(): class S1(Service): name = 'S1Fools' __namespace__ = 'Hippity' @rpc(String, _returns=String) def echo_string_s1(self, string): return string return S1 def TS2(): class S2(Service): name = 'S2Fools' __namespace__ = 'Hoppity' @rpc(String, _returns=String) def bobs(self, string): return string return S2 def TS3(): class S3(Service): name = 'S3Fools' __namespace__ = 'Hoppity' __service_name__ = 'BlahService' __port_types__ = ['bobhope', 'larry'] @rpc(String, _returns=String) def echo(self, string): return string @rpc(String, _port_type='bobhope', _returns=String) def echo_bob_hope(self, string): return 'Bob Hope' return S3 def TMissingRPCPortService(): class MissingRPCPortService(Service): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _returns=String) def raise_exception(self, string): return string return MissingRPCPortService def TBadRPCPortService(): class BadRPCPortService(Service): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _port_type='existingss', _returns=String) def raise_exception(self, string): return string return BadRPCPortService def TMissingServicePortService(): class MissingServicePortService(Service): name = 'MissingRPCPortService' __namespace__ = 'MissingRPCPortService' __service_name__ = 'MissingRPCPortService' __port_types__ = ['existing'] @rpc(String, _port_type='existingss', _returns=String) def raise_exception(self, string): return string return MissingServicePortService def TSinglePortService(): class SinglePortService(Service): name = 'SinglePort' __service_name__ = 'SinglePortService_ServiceInterface' __namespace__ = 'SinglePortNS' __port_types__ = ['FirstPortType'] @rpc(String, _port_type='FirstPortType', _returns=String) def echo_default_port_service(self, string): return string return SinglePortService def TDoublePortService(): class DoublePortService(Service): name = 'DoublePort' __namespace__ = 'DoublePort' __port_types__ = ['FirstPort', 'SecondPort'] @rpc(String, _port_type='FirstPort', _returns=String) def echo_first_port(self, string): return string @rpc(String, _port_type='SecondPort', _returns=String) def echo_second_port(self, string): return string return DoublePortService spyne-spyne-2.14.0/spyne/test/interface/wsdl/test_bindings.py000077500000000000000000000120301417664205300243330ustar00rootroot00000000000000#!/usr/bin/env python #encoding: utf8 # # spyne - Copyright (C) spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import unittest import spyne.const.xml as ns from spyne.interface.wsdl.wsdl11 import Wsdl11 from . import build_app from .port_service_services import TS1 from .port_service_services import TSinglePortService from .port_service_services import TDoublePortService class TestWSDLBindingBehavior(unittest.TestCase): def setUp(self): self.transport = 'http://schemas.xmlsoap.org/soap/http' self.url = 'http:/localhost:7789/wsdl' self.port_type_string = ns.WSDL11('portType') self.service_string = ns.WSDL11('service') self.binding_string = ns.WSDL11('binding') self.operation_string = ns.WSDL11('operation') self.port_string = ns.WSDL11('port') def test_binding_simple(self): sa = build_app([TS1()], 'S1Port', 'TestServiceName') interface_doc = Wsdl11(sa.interface) interface_doc.build_interface_document(self.url) services = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(services), 1) portTypes = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:portType', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(portTypes), 1) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % "S1", namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 1) def test_binding_multiple(self): SinglePortService, DoublePortService = TSinglePortService(), TDoublePortService() sa = build_app( [SinglePortService, DoublePortService], 'MultiServiceTns', 'AppName' ) interface_doc = Wsdl11(sa.interface) interface_doc.build_interface_document(self.url) # 2 Service, # First has 1 port # Second has 2 # => need 2 service, 3 port and 3 bindings services = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(services), 2) portTypes = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:portType', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(portTypes), 3) bindings = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:binding', namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(bindings), 3) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % SinglePortService.__service_name__, namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 1) ports = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' % "DoublePortService", namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(len(ports), 2) # checking name and type #service SinglePortService for srv in (SinglePortService, DoublePortService): for port in srv.__port_types__: bindings = interface_doc.root_elt.xpath( '/wsdl:definitions/wsdl:binding[@name="%s"]' % port, namespaces = { 'wsdl':'http://schemas.xmlsoap.org/wsdl/' }) self.assertEqual(bindings[0].get('type'), "tns:%s" % port) spyne-spyne-2.14.0/spyne/test/interface/wsdl/test_default_wsdl.py000077500000000000000000000201121417664205300252130ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import unittest from lxml import etree from spyne.application import Application from spyne.test.interface.wsdl import AppTestWrapper from spyne.test.interface.wsdl import build_app from spyne.test.interface.wsdl.defult_services import TDefaultPortService from spyne.test.interface.wsdl.defult_services import \ TDefaultPortServiceMultipleMethods from spyne.const import REQUEST_SUFFIX from spyne.const import RESPONSE_SUFFIX from spyne.const import ARRAY_SUFFIX from spyne.decorator import srpc from spyne.service import Service from spyne.interface.wsdl import Wsdl11 from spyne.model.complex import Array from spyne.model.primitive import String ns = { 'wsdl': 'http://schemas.xmlsoap.org/wsdl/', 'xs': 'http://www.w3.org/2001/XMLSchema', } class TestDefaultWSDLBehavior(unittest.TestCase): def _default_service(self, app_wrapper, service_name): self.assertEqual(1, len(app_wrapper.get_service_list())) services = app_wrapper.get_service_list() service = services[0] # the default behavior requires that there be only a single service self.assertEqual(1, len(services)) self.assertEqual(service_name, service.get('name')) # Test the default service has the correct number of ports # the default behavior requires that there be only a single port ports = app_wrapper.get_port_list(service) self.assertEqual(len(ports), 1) def _default_port_type(self, app_wrapper, portType_name, op_count): # Verify the portType Count portTypes = app_wrapper.get_port_types() # there should be only one portType self.assertEqual(1, len(portTypes)) # Verify the portType name portType = portTypes[0] # Check the name of the port self.assertEqual(portType_name, portType.get('name')) # verify that the portType definition has the correct # number of operations ops = app_wrapper.get_port_operations(portType) self.assertEqual(op_count, len(ops)) def _default_binding(self, wrapper, binding_name, opp_count): # the default behavior is only single binding bindings = wrapper.get_bindings() self.assertEqual(1, len(bindings)) # check for the correct binding name binding = bindings[0] name = binding.get('name') self.assertEqual(binding_name, name) # Test that the default service contains the soap binding sb = wrapper.get_soap_bindings(binding) self.assertEqual(1, len(sb)) # verify the correct number of operations ops = wrapper.get_binding_operations(binding) self.assertEqual(opp_count, len(ops)) def _default_binding_methods(self, wrapper, op_count, op_names): binding = wrapper.get_bindings()[0] operations = wrapper.get_binding_operations(binding) # Check the number of operations bound to the port self.assertEqual(op_count, len(operations)) # Check the operation names are correct for op in operations: self.assertTrue(op.get('name') in op_names) def test_default_port_type(self): # Test the default port is created # Test the default port has the correct name app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultPortName' ) wrapper = AppTestWrapper(app) self._default_port_type(wrapper, 'DefaultPortName', 1) def test_default_port_type_multiple(self): app = build_app( [TDefaultPortServiceMultipleMethods()], 'DefaultServiceTns', 'MultipleDefaultPortServiceApp' ) wrapper = AppTestWrapper(app) self._default_port_type(wrapper, "MultipleDefaultPortServiceApp", 3) def test_default_binding(self): app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultBindingName' ) wrapper = AppTestWrapper(app) self._default_binding(wrapper, "DefaultBindingName", 1) def test_default_binding_multiple(self): app = build_app( [TDefaultPortServiceMultipleMethods()], 'DefaultPortTest', 'MultipleDefaultBindingNameApp' ) wrapper = AppTestWrapper(app) self._default_binding(wrapper, 'MultipleDefaultBindingNameApp', 3) def test_default_binding_methods(self): app = build_app( [TDefaultPortService()], 'DefaultPortTest', 'DefaultPortMethods' ) wrapper = AppTestWrapper(app) self._default_binding_methods( wrapper, 1, ['echo_default_port_service'] ) def test_bare_simple(self): class SomeService(Service): @srpc(String, _returns=String, _body_style='bare') def whatever(ss): return ss app = Application([SomeService], tns='tns') app.transport = 'None' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('url') wsdl = etree.fromstring(wsdl.get_interface_document()) schema = wsdl.xpath( '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', namespaces=ns, ) assert len(schema) == 1 print(etree.tostring(wsdl, pretty_print=True)) elts = schema[0].xpath( 'xs:element[@name="whatever%s"]' % REQUEST_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'xs:string' elts = schema[0].xpath( 'xs:element[@name="whatever%s"]' % RESPONSE_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'xs:string' def test_bare_with_conflicting_types(self): class SomeService(Service): @srpc(Array(String), _returns=Array(String)) def whatever(sa): return sa @srpc(Array(String), _returns=Array(String), _body_style='bare') def whatever_bare(sa): return sa app = Application([SomeService], tns='tns') app.transport = 'None' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('url') wsdl = etree.fromstring(wsdl.get_interface_document()) schema, = wsdl.xpath( '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace="tns"]', namespaces=ns, ) print(etree.tostring(schema, pretty_print=True)) assert len(schema.xpath( 'xs:complexType[@name="string%s"]' % ARRAY_SUFFIX, namespaces=ns)) > 0 elts = schema.xpath( 'xs:element[@name="whatever_bare%s"]' % REQUEST_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX elts = schema.xpath( 'xs:element[@name="whatever_bare%s"]' % RESPONSE_SUFFIX, namespaces=ns) assert len(elts) > 0 assert elts[0].attrib['type'] == 'tns:string%s' % ARRAY_SUFFIX if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interface/wsdl/test_op_req_suffix.py000077500000000000000000000255231417664205300254220ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 import unittest from webtest import TestApp as _TestApp # avoid confusing py.test from spyne.application import Application from spyne.decorator import srpc from spyne.service import Service from spyne.model.primitive import Integer, Unicode from spyne.model.complex import Iterable from spyne.protocol.soap import Soap11 from spyne.protocol.http import HttpRpc from spyne.protocol.json import JsonDocument from spyne.server.wsgi import WsgiApplication from spyne.const.xml import PREFMAP, NS_WSDL11_SOAP def strip_whitespace(string): return ''.join(string.split()) class TestOperationRequestSuffix(unittest.TestCase): """ test different protocols with REQUEST_SUFFIX and _operation_name _in_message_name is a concern, will test that as well """ default_function_name = 'echo' # output is not affected, will use soap output for all tests result_body = ''' Echo, test Echo, test ''' def get_function_names(self, suffix, _operation_name=None, _in_message_name=None): """This tests the logic of how names are produced. Its logic should match expected behavior of the decorator. returns operation name, in message name, service name depending on args""" function_name = self.default_function_name if _operation_name is None: operation_name = function_name else: operation_name = _operation_name if _in_message_name is None: request_name = operation_name + suffix else: request_name = _in_message_name return function_name, operation_name, request_name def get_app(self, in_protocol, suffix, _operation_name=None, _in_message_name=None): """setup testapp dependent on suffix and _in_message_name""" import spyne.const spyne.const.REQUEST_SUFFIX = suffix class EchoService(Service): srpc_kparams = {'_returns': Iterable(Unicode)} if _in_message_name: srpc_kparams['_in_message_name'] = _in_message_name if _operation_name: srpc_kparams['_operation_name'] = _operation_name @srpc(Unicode, Integer, **srpc_kparams) def echo(string, times): for i in range(times): yield 'Echo, %s' % string application = Application([EchoService], tns='spyne.examples.echo', in_protocol=in_protocol, out_protocol=Soap11() ) app = WsgiApplication(application) testapp = _TestApp(app) # so that it doesn't interfere with other tests. spyne.const.REQUEST_SUFFIX = '' return testapp def assert_response_ok(self, resp): """check the default response""" self.assertEqual(resp.status_int, 200, resp) self.assertTrue( strip_whitespace(self.result_body) in strip_whitespace(str(resp)), '{0} not in {1}'.format(self.result_body, resp)) ### application error tests ### def assert_application_error(self, suffix, _operation_name=None, _in_message_name=None): self.assertRaises(ValueError, self.get_app, Soap11(validator='lxml'), suffix, _operation_name, _in_message_name) def test_assert_application_error(self): """check error when op namd and in name are both used""" self.assert_application_error(suffix='', _operation_name='TestOperationName', _in_message_name='TestMessageName') ### soap tests ### def assert_soap_ok(self, suffix, _operation_name=None, _in_message_name=None): """helper to test soap requests""" # setup app = self.get_app(Soap11(validator='lxml'), suffix, _operation_name, _in_message_name) function_name, operation_name, request_name = self.get_function_names( suffix, _operation_name, _in_message_name) soap_input_body = """ test 2 """.format(request_name) # check wsdl wsdl = app.get('/?wsdl') self.assertEqual(wsdl.status_int, 200, wsdl) self.assertTrue(request_name in wsdl, '{0} not found in wsdl'.format(request_name)) soap_strings = [ ''.format(request_name), ''.format(request_name), ] for soap_string in soap_strings: self.assertTrue(soap_string in wsdl, '{0} not in {1}'.format(soap_string, wsdl)) if request_name != operation_name: wrong_string = '= 2.5") spyne-spyne-2.14.0/spyne/test/interop/server/httprpc_pod_basic.py000077500000000000000000000040031417664205300252440ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """pod being plain old data""" import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.test.interop.server import get_open_port from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.server.wsgi import WsgiApplication httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.pod', in_protocol=HttpRpc(), out_protocol=HttpRpc()) host = "127.0.0.1" port = [0] def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator if port[0] == 0: port[0] = get_open_port() wsgi_application = WsgiApplication(httprpc_soap_application) server = make_server(host, port[0], validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-spyne-2.14.0/spyne/test/interop/server/httprpc_pod_basic_twisted.py000077500000000000000000000037371417664205300270240ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """pod being plain old data""" import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.test.interop.server import get_open_port from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.server.twisted import TwistedWebResource httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.pod', in_protocol=HttpRpc(), out_protocol=HttpRpc()) host = '127.0.0.1' port = [0] def main(argv): from twisted.web.server import Site from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) if port[0] == 0: port[0] = get_open_port() wr = TwistedWebResource(httprpc_soap_application) site = Site(wr) reactor.listenTCP(port[0], site) logging.info("listening on: %s:%d" % (host,port[0])) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-spyne-2.14.0/spyne/test/interop/server/msgpackrpc_http_basic.py000066400000000000000000000040271417664205300261120ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.msgpack').setLevel(logging.DEBUG) logger = logging.getLogger('spyne.test.interop.server.msgpackrpc_http_basic') from spyne.test.interop.server import get_open_port from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server._service import services from spyne.application import Application from spyne.protocol.msgpack import MessagePackRpc msgpackrpc_application = Application(services, 'spyne.test.interop.server', in_protocol=MessagePackRpc(validator='soft'), out_protocol=MessagePackRpc()) host = '127.0.0.1' port = [0] def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator if port[0] == 0: port[0] = get_open_port() wsgi_application = WsgiApplication(msgpackrpc_application) server = make_server(host, port[0], validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % (host, port[0])) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-spyne-2.14.0/spyne/test/interop/server/soap11/000077500000000000000000000000001417664205300223075ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interop/server/soap11/__init__.py000066400000000000000000000000001417664205300244060ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interop/server/soap11/httprpc_soap_basic.py000077500000000000000000000036661417664205300265460ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server import get_open_port httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.soap', in_protocol=HttpRpc(), out_protocol=Soap11()) host = '127.0.0.1' port = [0] if __name__ == '__main__': try: from wsgiref.simple_server import make_server from wsgiref.validate import validator if port[0] == 0: port[0] = get_open_port() wsgi_application = WsgiApplication(httprpc_soap_application) server = make_server(host, port[0], validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") spyne-spyne-2.14.0/spyne/test/interop/server/soap11/soap_http_basic.py000077500000000000000000000040161417664205300260270ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logger = logging.getLogger('spyne.test.interop.server.soap_http_basic') from spyne.test.interop.server import get_open_port from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server._service import services from spyne.application import Application from spyne.protocol.soap import Soap11 soap11_application = Application(services, 'spyne.test.interop.server', in_protocol=Soap11(validator='lxml', cleanup_namespaces=True), out_protocol=Soap11()) host = '127.0.0.1' port = [0] def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator if port[0] == 0: port[0] = get_open_port() wsgi_application = WsgiApplication(soap11_application) server = make_server(host, port[0], validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', port[0])) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-spyne-2.14.0/spyne/test/interop/server/soap11/soap_http_basic_twisted.py000077500000000000000000000032471417664205300275770ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) from spyne.test.interop.server import get_open_port from spyne.test.interop.server.soap_http_basic import soap11_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = [0] def main(argv): from twisted.web.server import Site from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) wr = TwistedWebResource(soap11_application) site = Site(wr) if port[0] == 0: port[0] = get_open_port() reactor.listenTCP(port[0], site) logging.info("listening on: %s:%d" % (host,port[0])) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-spyne-2.14.0/spyne/test/interop/server/soap11/soap_http_static.py000077500000000000000000000037411417664205300262410ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) import os from spyne.test.interop.server import get_open_port from spyne.test.interop.server.soap_http_basic import soap11_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = [0] url = 'app' def main(argv): from twisted.python import log from twisted.web.server import Site from twisted.web.static import File from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) static_dir = os.path.abspath('.') logging.info("registering static folder %r on /" % static_dir) root = File(static_dir) wr = TwistedWebResource(soap11_application) logging.info("registering %r on /%s" % (wr, url)) root.putChild(url, wr) site = Site(root) if port[0] == 0: port[0] = get_open_port() reactor.listenTCP(port[0], site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-spyne-2.14.0/spyne/test/interop/server/soap11/soap_zeromq.py000077500000000000000000000032701417664205300252250ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging from spyne.test.interop.server import get_open_port from spyne.test.interop.server.soap11.soap_http_basic import soap11_application from spyne.server.zeromq import ZeroMQServer host = '127.0.0.1' port = [0] def main(): if port[0] == 0: port[0] = get_open_port() url = "tcp://%s:%d" % (host, port[0]) logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) server = ZeroMQServer(soap11_application, url) logging.info("************************") logging.info("Use Ctrl+\\ to exit if Ctrl-C does not work.") logging.info("See the 'I can't Ctrl-C my Python/Ruby application. Help!' " "question in http://www.zeromq.org/area:faq for more info.") logging.info("listening on %r" % url) logging.info("************************") server.serve_forever() if __name__ == '__main__': main()spyne-spyne-2.14.0/spyne/test/interop/server/soap12/000077500000000000000000000000001417664205300223105ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interop/server/soap12/__init__.py000066400000000000000000000000001417664205300244070ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/interop/server/soap12/httprpc_soap_basic.py000077500000000000000000000034731417664205300265430ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.protocol.xml') logger.setLevel(logging.DEBUG) from spyne.application import Application from spyne.test.interop.server._service import services from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap12 from spyne.server.wsgi import WsgiApplication httprpc_soap_application = Application(services, 'spyne.test.interop.server.httprpc.soap', in_protocol=HttpRpc(), out_protocol=Soap12()) host = '127.0.0.1' port = 9753 if __name__ == '__main__': try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(httprpc_soap_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9753)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") spyne-spyne-2.14.0/spyne/test/interop/server/soap12/soap_http_basic.py000077500000000000000000000036221417664205300260320ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) logger = logging.getLogger('spyne.test.interop.server.soap_http_basic') from spyne.server.wsgi import WsgiApplication from spyne.test.interop.server._service import services from spyne.application import Application from spyne.protocol.soap import Soap12 soap12_application = Application(services, 'spyne.test.interop.server', in_protocol=Soap12(validator='lxml', cleanup_namespaces=True), out_protocol=Soap12()) host = '127.0.0.1' port = 9754 def main(): try: from wsgiref.simple_server import make_server from wsgiref.validate import validator wsgi_application = WsgiApplication(soap12_application) server = make_server(host, port, validator(wsgi_application)) logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9754)) logger.info('WSDL is at: /?wsdl') server.serve_forever() except ImportError: print("Error: example server code requires Python >= 2.5") if __name__ == '__main__': main() spyne-spyne-2.14.0/spyne/test/interop/server/soap12/soap_http_basic_twisted.py000077500000000000000000000030741417664205300275760ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) from spyne.test.interop.server.soap12.soap_http_basic import soap12_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = 9755 def main(argv): from twisted.web.server import Site from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) wr = TwistedWebResource(soap12_application) site = Site(wr) reactor.listenTCP(port, site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-spyne-2.14.0/spyne/test/interop/server/soap12/soap_http_static.py000077500000000000000000000035711417664205300262430ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('spyne.wsgi') logger.setLevel(logging.DEBUG) import os from spyne.test.interop.server.soap12.soap_http_basic import soap12_application from spyne.server.twisted import TwistedWebResource host = '127.0.0.1' port = 9756 url = 'app' def main(argv): from twisted.python import log from twisted.web.server import Site from twisted.web.static import File from twisted.internet import reactor from twisted.python import log observer = log.PythonLoggingObserver('twisted') log.startLoggingWithObserver(observer.emit, setStdout=False) static_dir = os.path.abspath('.') logging.info("registering static folder %r on /" % static_dir) root = File(static_dir) wr = TwistedWebResource(soap12_application) logging.info("registering %r on /%s" % (wr, url)) root.putChild(url, wr) site = Site(root) reactor.listenTCP(port, site) logging.info("listening on: %s:%d" % (host,port)) return reactor.run() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) spyne-spyne-2.14.0/spyne/test/interop/server/soap12/soap_zeromq.py000077500000000000000000000031101417664205300252170ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging from spyne.test.interop.server.soap12.soap_http_basic import soap12_application from spyne.server.zeromq import ZeroMQServer host = '127.0.0.1' port = 55555 def main(): url = "tcp://%s:%d" % (host,port) logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) server = ZeroMQServer(soap12_application, url) logging.info("************************") logging.info("Use Ctrl+\\ to exit if Ctrl-C does not work.") logging.info("See the 'I can't Ctrl-C my Python/Ruby application. Help!' " "question in http://www.zeromq.org/area:faq for more info.") logging.info("listening on %r" % url) logging.info("************************") server.serve_forever() if __name__ == '__main__': main()spyne-spyne-2.14.0/spyne/test/interop/test_django.py000077500000000000000000000311771417664205300225640ustar00rootroot00000000000000# coding: utf-8 #!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import import datetime import re from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, TransactionTestCase, Client from spyne.client.django import DjangoTestClient from spyne.model.fault import Fault from spyne.model.complex import ComplexModelBase from spyne.util.django import (DjangoComplexModel, DjangoComplexModelMeta, email_re) from spyne.util.six import add_metaclass from rpctest.core.models import (FieldContainer, RelatedFieldContainer, UserProfile as DjUserProfile) from rpctest.core.views import app, hello_world_service, Container class SpyneTestCase(TransactionTestCase): def setUp(self): self.client = DjangoTestClient('/hello_world/', hello_world_service.app) def _test_say_hello(self): resp = self.client.service.say_hello('Joe', 5) list_resp = list(resp) self.assertEqual(len(list_resp), 5) self.assertEqual(list_resp, ['Hello, Joe'] * 5) class DjangoViewTestCase(TestCase): def test_say_hello(self): client = DjangoTestClient('/say_hello/', app) resp = client.service.say_hello('Joe', 5) list_resp = list(resp) self.assertEqual(len(list_resp), 5) self.assertEqual(list_resp, ['Hello, Joe'] * 5) def test_response_encoding(self): client = DjangoTestClient('/say_hello/', app) response = client.service.say_hello.get_django_response('Joe', 5) self.assertTrue('Content-Type' in response) self.assertTrue(response['Content-Type'].startswith('text/xml')) def test_error(self): client = Client() response = client.post('/say_hello/', {}) self.assertContains(response, 'faultstring', status_code=500) def test_cached_wsdl(self): """Test if wsdl is cached.""" client = Client() response = client.get('/say_hello/') self.assertContains(response, 'location="http://testserver/say_hello/"') response = client.get('/say_hello/', HTTP_HOST='newtestserver') self.assertNotContains(response, 'location="http://newtestserver/say_hello/"') def test_not_cached_wsdl(self): """Test if wsdl is not cached.""" client = Client() response = client.get('/say_hello_not_cached/') self.assertContains( response, 'location="http://testserver/say_hello_not_cached/"') response = client.get('/say_hello_not_cached/', HTTP_HOST='newtestserver') self.assertContains( response, 'location="http://newtestserver/say_hello_not_cached/"') class ModelTestCase(TestCase): """Test mapping between django and spyne models.""" def setUp(self): self.client = DjangoTestClient('/api/', app) def test_exclude(self): """Test if excluded field is not mapped.""" type_info = Container.get_flat_type_info(Container) self.assertIn('id', type_info) self.assertNotIn('excluded_field', type_info) def test_pk_mapping(self): """Test if primary key is mapped as optional but not nillable.""" type_info = Container.get_flat_type_info(Container) pk_field = type_info['id'] self.assertEqual(pk_field.Attributes.min_occurs, 0) self.assertFalse(pk_field.Attributes.nullable) def test_regex_pattern_mapping(self): """Test if regex pattern is mapped from django model.""" type_info = Container.get_flat_type_info(Container) email_field = type_info['email_field'] self.assertEqual(email_field.__name__, 'Unicode') self.assertIsNotNone(email_field.Attributes.pattern) self.assertEqual(email_field.Attributes.min_occurs, 1) self.assertFalse(email_field.Attributes.nullable) def test_blank_field(self): """Test if blank fields are optional but not null.""" type_info = Container.get_flat_type_info(Container) blank_field = type_info['blank_field'] self.assertEqual(blank_field.__name__, 'NormalizedString') self.assertEqual(blank_field.Attributes.min_occurs, 0) self.assertFalse(blank_field.Attributes.nullable) def test_blank_as_dict(self): """Test if blank field is omitted in as_dict representation.""" container = Container() container_dict = container.as_dict() self.assertNotIn('blank_field', container_dict) def test_length_validators_field(self): """Test if length validators are correctly mapped.""" type_info = Container.get_flat_type_info(Container) length_validators_field = type_info['length_validators_field'] self.assertEqual(length_validators_field.__name__, 'NormalizedString') self.assertEqual(length_validators_field.Attributes.min_occurs, 1) self.assertTrue(length_validators_field.Attributes.nullable) self.assertEqual(length_validators_field.Attributes.min_len, 3) self.assertEqual(length_validators_field.Attributes.max_len, 10) def test_get_container(self): """Test mapping from Django model to spyne model.""" get_container = lambda: self.client.service.get_container(2) self.assertRaises(Fault, get_container) container = FieldContainer.objects.create(slug_field='container') FieldContainer.objects.create(slug_field='container2', foreign_key=container, one_to_one_field=container, email_field='email@example.com', char_field='yo') c = get_container() self.assertIsInstance(c, Container) def test_create_container(self): """Test complex input to create Django model.""" related_container = RelatedFieldContainer(id='related') new_container = FieldContainer(slug_field='container', date_field=datetime.date.today(), datetime_field=datetime.datetime.now(), email_field='email@example.com', time_field=datetime.time(), custom_foreign_key=related_container, custom_one_to_one_field=related_container) create_container = (lambda: self.client.service.create_container( new_container)) c = create_container() self.assertIsInstance(c, Container) self.assertEqual(c.custom_one_to_one_field_id, 'related') self.assertEqual(c.custom_foreign_key_id, 'related') self.assertRaises(Fault, create_container) def test_create_container_unicode(self): """Test complex unicode input to create Django model.""" new_container = FieldContainer( char_field=u'спайн', text_field=u'спайн', slug_field='spyne', email_field='email@example.com', date_field=datetime.date.today(), datetime_field=datetime.datetime.now(), time_field=datetime.time() ) create_container = (lambda: self.client.service.create_container( new_container)) c = create_container() self.assertIsInstance(c, Container) self.assertRaises(Fault, create_container) def test_optional_relation_fields(self): """Test if optional_relations flag makes fields optional.""" class UserProfile(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = DjUserProfile self.assertFalse(UserProfile._type_info['user_id'].Attributes.nullable) class UserProfile(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = DjUserProfile django_optional_relations = True self.assertEqual( UserProfile._type_info['user_id'].Attributes.min_occurs, 0) def test_abstract_custom_djangomodel(self): """Test if can create custom DjangoComplexModel.""" @add_metaclass(DjangoComplexModelMeta) class OrderedDjangoComplexModel(ComplexModelBase): __abstract__ = True class Attributes(ComplexModelBase.Attributes): declare_order = 'declared' class OrderedFieldContainer(OrderedDjangoComplexModel): class Attributes(OrderedDjangoComplexModel.Attributes): django_model = FieldContainer field_container = OrderedFieldContainer() type_info_fields = field_container._type_info.keys() django_field_names = [field.get_attname() for field in FieldContainer._meta.fields] # file field is not mapped django_field_names.remove('file_field') # check if ordering is the same as defined in Django model self.assertEqual(type_info_fields, django_field_names) def test_nonabstract_custom_djangomodel(self): """Test if can't create non abstract custom model.""" with self.assertRaises( ImproperlyConfigured, msg='Can create non abstract custom model' ): @add_metaclass(DjangoComplexModelMeta) class CustomNotAbstractDjangoComplexModel(ComplexModelBase): class Attributes(ComplexModelBase.Attributes): declare_order = 'declared' # in XmlSchema ^ and $ are set implicitly python_email_re = '^' + email_re.pattern + '$' class EmailRegexTestCase(TestCase): """Tests for email_re.""" def test_empty(self): """Empty string is invalid email.""" self.assertIsNone(re.match(python_email_re, '')) def test_valid(self): """Test valid email.""" self.assertIsNotNone( re.match(python_email_re, 'valid.email@example.com') ) def test_valid_single_letter_domain(self): """Test valid email.""" self.assertIsNotNone(re.match(python_email_re, 'valid.email@e.x.com')) def test_invalid(self): """Test invalid email.""" self.assertIsNone(re.match(python_email_re, '@example.com')) def test_invalid_tld(self): """Test if email from Top Level Domain is invalid.""" self.assertIsNone(re.match(python_email_re, 'babushka@email')) self.assertIsNone(re.match(python_email_re, 'babushka@domain.email-')) class DjangoServiceTestCase(TestCase): """Tests for Django specific service.""" def test_handle_does_not_exist(self): """Test if Django service handles `ObjectDoesNotExist` exceptions.""" client = DjangoTestClient('/api/', app) with self.assertRaisesRegexp(Fault, 'Client.FieldContainerNotFound'): client.service.raise_does_not_exist() def test_handle_validation_error(self): """Test if Django service handles `ValidationError` exceptions.""" client = DjangoTestClient('/api/', app) with self.assertRaisesRegexp(Fault, 'Client.ValidationError'): client.service.raise_validation_error() class FromUnicodeAssertionTestCase(TestCase): def test_from_unicode_does_not_assert(self): client = Client() url = '/synchro/1/' msg = b"""TestModel 2015-09-23T13:54:51.796366+00:00 """ hdrs = {'SOAPAction': b'"sync"', 'Content-Type': 'text/xml; charset=utf-8'} client.post(url, msg, 'text/xml', True, **hdrs) spyne-spyne-2.14.0/spyne/test/interop/test_httprpc.py000077500000000000000000000062611417664205300230020ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest import time import pytz from datetime import datetime from spyne.test.interop._test_soap_client_base import server_started from spyne.util import thread, urlencode, urlopen, Request, HTTPError _server_started = False class TestHttpRpc(unittest.TestCase): def setUp(self): global _server_started from spyne.test.interop.server.httprpc_pod_basic import main, port if not _server_started: def run_server(): main() thread.start_new_thread(run_server, ()) # FIXME: Does anybody have a better idea? time.sleep(2) _server_started = True self.base_url = 'http://localhost:%d' % port[0] def test_404(self): url = '%s/404' % self.base_url try: data = urlopen(url).read() except HTTPError as e: assert e.code == 404 def test_413(self): url = self.base_url try: data = Request(url,("foo"*3*1024*1024)) except HTTPError as e: assert e.code == 413 def test_500(self): url = '%s/python_exception' % self.base_url try: data = urlopen(url).read() except HTTPError as e: assert e.code == 500 def test_500_2(self): url = '%s/soap_exception' % self.base_url try: data = urlopen(url).read() except HTTPError as e: assert e.code == 500 def test_echo_string(self): url = '%s/echo_string?s=punk' % self.base_url data = urlopen(url).read() assert data == b'punk' def test_echo_integer(self): url = '%s/echo_integer?i=444' % self.base_url data = urlopen(url).read() assert data == b'444' def test_echo_datetime(self): dt = datetime.now(pytz.utc).isoformat().encode('ascii') params = urlencode({ 'dt': dt, }) print(params) url = '%s/echo_datetime?%s' % (self.base_url, str(params)) data = urlopen(url).read() assert dt == data def test_echo_datetime_tz(self): dt = datetime.now(pytz.utc).isoformat().encode('ascii') params = urlencode({ 'dt': dt, }) print(params) url = '%s/echo_datetime?%s' % (self.base_url, str(params)) data = urlopen(url).read() assert dt == data if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/test_msgpackrpc_client_http.py000066400000000000000000000032231417664205300260350ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.client.http import HttpClient from spyne.test.interop._test_soap_client_base import SpyneClientTestBase from spyne.test.interop.server.msgpackrpc_http_basic import msgpackrpc_application, port from spyne.util.etreeconv import root_dict_to_etree class TestSpyneHttpClient(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'msgpack_rpc_http') self.client = HttpClient('http://localhost:%d/' % port[0], msgpackrpc_application) self.ns = "spyne.test.interop.server" @unittest.skip("MessagePackRpc does not support header") def test_echo_in_header(self): pass @unittest.skip("MessagePackRpc does not support header") def test_send_out_header(self): pass if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/test_pyramid.py000077500000000000000000000052521417664205300227620ustar00rootroot00000000000000# coding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from wsgiref.util import setup_testing_defaults from wsgiref.validate import validator from lxml import etree from pyramid import testing from pyramid.config import Configurator from pyramid.request import Request from spyne.protocol.soap import Soap11 from spyne.service import Service from spyne.decorator import srpc from spyne import Application from spyne.model import Unicode, Integer, Iterable from spyne.server.pyramid import PyramidApplication class SpyneIntegrationTest(unittest.TestCase): """Tests for integration of Spyne into Pyramid view callable""" class HelloWorldService(Service): @srpc(Unicode, Integer, _returns=Iterable(Unicode)) def say_hello(name, times): for i in range(times): yield 'Hello, %s' % name def setUp(self): request = testing.DummyRequest() self.config = testing.setUp(request=request) def tearDown(self): testing.tearDown() def testGetWsdl(self): """Simple test for serving of WSDL by spyne through pyramid route""" application = PyramidApplication( Application([self.HelloWorldService], tns='spyne.examples.hello', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11())) config = Configurator(settings={'debug_all': True}) config.add_route('home', '/') config.add_view(application, route_name='home') wsgi_app = validator(config.make_wsgi_app()) env = { 'SCRIPT_NAME': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'QUERY_STRING': 'wsdl', } setup_testing_defaults(env) request = Request(env) resp = request.get_response(wsgi_app) self.assert_(resp.status.startswith("200 ")) node = etree.XML(resp.body) # will throw exception if non well formed spyne-spyne-2.14.0/spyne/test/interop/test_rpc.rb000077500000000000000000000007361417664205300220560ustar00rootroot00000000000000#!/usr/bin/ruby class InteropTest require 'soap/rpc/driver' def initialize() ws_url='http://127.0.0.1:9754/' ws_ns='InteropService.InteropService' @conn = SOAP::RPC::Driver.new(ws_url, ws_ns) end def echo_int(i) @conn.add_method("echo_integer", "i") @conn.echo_integer(i) end def echo_string(s) @conn.add_method("echo_string", "s") @conn.echo_string(s) end end ws = InteropTest.new() puts ws.echo_string("OK") puts ws.echo_int(0) spyne-spyne-2.14.0/spyne/test/interop/test_soap_client_http.py000077500000000000000000000026471417664205300246610ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.client.http import HttpClient from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ server_started from spyne.test.interop.server.soap11.soap_http_basic import soap11_application class TestSpyneHttpClient(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'http') port, = server_started.keys() self.client = HttpClient('http://localhost:%d/' % port, soap11_application) self.ns = "spyne.test.interop.server" if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/test_soap_client_http_twisted.py000077500000000000000000000043541417664205300264210ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from twisted.trial import unittest from spyne.test.interop._test_soap_client_base import run_server, server_started from spyne.client.twisted import TwistedHttpClient from spyne.test.interop.server.soap11.soap_http_basic import soap11_application class TestSpyneHttpClient(unittest.TestCase): def setUp(self): run_server('http') port, = server_started.keys() self.ns = b"spyne.test.interop.server._service" self.client = TwistedHttpClient(b'http://localhost:%d/' % port, soap11_application) def test_echo_boolean(self): def eb(ret): raise ret def cb(ret): assert ret == True return self.client.service.echo_boolean(True).addCallbacks(cb, eb) def test_python_exception(self): def eb(ret): print(ret) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.python_exception().addCallbacks(cb, eb) def test_soap_exception(self): def eb(ret): print(type(ret)) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.soap_exception().addCallbacks(cb, eb) def test_documented_exception(self): def eb(ret): print(ret) def cb(ret): assert False, "must fail: %r" % ret return self.client.service.python_exception().addCallbacks(cb, eb) spyne-spyne-2.14.0/spyne/test/interop/test_soap_client_zeromq.py000077500000000000000000000026671417664205300252210ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.client.zeromq import ZeroMQClient from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ server_started from spyne.test.interop.server.soap11.soap_http_basic import soap11_application class TestSpyneZmqClient(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'zeromq') port, = server_started.keys() self.client = ZeroMQClient('tcp://localhost:%d' % port, soap11_application) self.ns = "spyne.test.interop.server._service" if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/test_suds.py000077500000000000000000000353211417664205300222730ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging suds_logger = logging.getLogger('suds') suds_logger.setLevel(logging.INFO) import unittest from datetime import datetime from base64 import b64encode, b64decode from suds.sax.parser import Parser from suds.client import Client from suds.plugin import MessagePlugin from suds import WebFault from spyne.util import six from spyne.test.interop._test_soap_client_base import SpyneClientTestBase, \ server_started class LastReceivedPlugin(MessagePlugin): def received(self, context): sax = Parser() self.reply = sax.parse(string=context.reply) class TestSuds(SpyneClientTestBase, unittest.TestCase): def setUp(self): SpyneClientTestBase.setUp(self, 'http') port, = server_started.keys() self.client = Client("http://localhost:%d/?wsdl" % port, cache=None, plugins=[LastReceivedPlugin()]) self.ns = "spyne.test.interop.server" def test_echo_datetime(self): val = datetime.now() ret = self.client.service.echo_datetime(val) assert val == ret def test_echo_datetime_with_invalid_format(self): val = datetime.now() ret = self.client.service.echo_datetime_with_invalid_format(val) assert val == ret def test_echo_date(self): val = datetime.now().date() ret = self.client.service.echo_date(val) assert val == ret def test_echo_date_with_invalid_format(self): val = datetime.now().date() ret = self.client.service.echo_date_with_invalid_format(val) assert val == ret def test_echo_time(self): val = datetime.now().time() ret = self.client.service.echo_time(val) assert val == ret def test_echo_time_with_invalid_format(self): val = datetime.now().time() ret = self.client.service.echo_time_with_invalid_format(val) assert val == ret def test_echo_simple_boolean_array(self): val = [False, False, False, True] ret = self.client.service.echo_simple_boolean_array(val) assert val == ret def test_echo_boolean(self): val = True ret = self.client.service.echo_boolean(val) self.assertEqual(val, ret) val = False ret = self.client.service.echo_boolean(val) self.assertEqual(val, ret) def test_enum(self): DaysOfWeekEnum = self.client.factory.create("DaysOfWeekEnum") val = DaysOfWeekEnum.Monday ret = self.client.service.echo_enum(val) assert val == ret def test_bytearray(self): val = b"\x00\x01\x02\x03\x04" # suds doesn't support base64 encoding, so we do it manually ret = self.client.service.echo_bytearray(b64encode(val).decode()) assert val == b64decode(ret) def test_validation(self): non_nillable_class = self.client.factory.create( "{hunk.sunk}NonNillableClass") non_nillable_class.i = 6 non_nillable_class.s = None try: self.client.service.non_nillable(non_nillable_class) except WebFault as e: pass else: raise Exception("must fail") def test_echo_integer_array(self): ia = self.client.factory.create('integerArray') ia.integer.extend([1, 2, 3, 4, 5]) self.client.service.echo_integer_array(ia) def test_echo_in_header(self): in_header = self.client.factory.create('InHeader') in_header.s = 'a' in_header.i = 3 self.client.set_options(soapheaders=in_header) ret = self.client.service.echo_in_header() self.client.set_options(soapheaders=None) print(ret) self.assertEqual(in_header.s, ret.s) self.assertEqual(in_header.i, ret.i) def test_echo_in_complex_header(self): in_header = self.client.factory.create('InHeader') in_header.s = 'a' in_header.i = 3 in_trace_header = self.client.factory.create('InTraceHeader') in_trace_header.client = 'suds' in_trace_header.callDate = datetime(year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0) self.client.set_options(soapheaders=(in_header, in_trace_header)) ret = self.client.service.echo_in_complex_header() self.client.set_options(soapheaders=None) print(ret) self.assertEqual(in_header.s, ret[0].s) self.assertEqual(in_header.i, ret[0].i) self.assertEqual(in_trace_header.client, ret[1].client) self.assertEqual(in_trace_header.callDate, ret[1].callDate) def test_send_out_header(self): out_header = self.client.factory.create('OutHeader') out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 ret = self.client.service.send_out_header() self.assertTrue(isinstance(ret, type(out_header))) self.assertEqual(ret.dt, out_header.dt) self.assertEqual(ret.f, out_header.f) def test_send_out_complex_header(self): out_header = self.client.factory.create('OutHeader') out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 out_trace_header = self.client.factory.create('OutTraceHeader') out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=1) out_trace_header.returnDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=100) ret = self.client.service.send_out_complex_header() self.assertTrue(isinstance(ret[0], type(out_header))) self.assertEqual(ret[0].dt, out_header.dt) self.assertEqual(ret[0].f, out_header.f) self.assertTrue(isinstance(ret[1], type(out_trace_header))) self.assertEqual(ret[1].receiptDate, out_trace_header.receiptDate) self.assertEqual(ret[1].returnDate, out_trace_header.returnDate) # Control the reply soap header (in an unelegant way but this is the # only way with suds) soapheaders = self.client.options.plugins[0].reply.getChild("Envelope").getChild("Header") soap_out_header = soapheaders.getChild('OutHeader') self.assertEqual('T'.join((out_header.dt.date().isoformat(), out_header.dt.time().isoformat())), soap_out_header.getChild('dt').getText()) self.assertEqual(six.text_type(out_header.f), soap_out_header.getChild('f').getText()) soap_out_trace_header = soapheaders.getChild('OutTraceHeader') self.assertEqual('T'.join((out_trace_header.receiptDate.date().isoformat(), out_trace_header.receiptDate.time().isoformat())), soap_out_trace_header.getChild('receiptDate').getText()) self.assertEqual('T'.join((out_trace_header.returnDate.date().isoformat(), out_trace_header.returnDate.time().isoformat())), soap_out_trace_header.getChild('returnDate').getText()) def test_echo_string(self): test_string = "OK" ret = self.client.service.echo_string(test_string) self.assertEqual(ret, test_string) def __get_xml_test_val(self): return { "test_sub": { "test_subsub1": { "test_subsubsub1": ["subsubsub1 value"] }, "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], "test_subsub3": [ { "test_subsub3sub1": ["subsub3sub1 value"] }, { "test_subsub3sub2": ["subsub3sub2 value"] }, ], "test_subsub4": [], "test_subsub5": ["x"], } } def test_echo_simple_class(self): val = self.client.factory.create("{spyne.test.interop.server}SimpleClass") val.i = 45 val.s = "asd" ret = self.client.service.echo_simple_class(val) assert ret.i == val.i assert ret.s == val.s def test_echo_class_with_self_reference(self): val = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") val.i = 45 val.sr = self.client.factory.create("{spyne.test.interop.server}ClassWithSelfReference") val.sr.i = 50 val.sr.sr = None ret = self.client.service.echo_class_with_self_reference(val) assert ret.i == val.i assert ret.sr.i == val.sr.i def test_echo_nested_class(self): val = self.client.factory.create("{punk.tunk}NestedClass"); val.i = 45 val.s = "asd" val.f = 12.34 val.ai = self.client.factory.create("integerArray") val.ai.integer.extend([1, 2, 3, 45, 5, 3, 2, 1, 4]) val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True ret = self.client.service.echo_nested_class(val) self.assertEqual(ret.i, val.i) self.assertEqual(ret.ai[0], val.ai[0]) self.assertEqual(ret.simple.SimpleClass[0].s, val.simple.SimpleClass[0].s) self.assertEqual(ret.other.dt, val.other.dt) def test_huge_number(self): self.assertEqual(self.client.service.huge_number(), 2 ** int(1e5)) def test_long_string(self): self.assertEqual(self.client.service.long_string(), ('0123456789abcdef' * 16384)) def test_empty(self): self.client.service.test_empty() def test_echo_extension_class(self): val = self.client.factory.create("{bar}ExtensionClass") val.i = 45 val.s = "asd" val.f = 12.34 val.simple = self.client.factory.create("{spyne.test.interop.server}SimpleClassArray") val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass.append(self.client.factory.create("{spyne.test.interop.server}SimpleClass")) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.factory.create("{spyne.test.interop.server}OtherClass"); val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True val.p = self.client.factory.create("{hunk.sunk}NonNillableClass"); val.p.dt = datetime(2010, 6, 2) val.p.i = 123 val.p.s = "punk" val.l = datetime(2010, 7, 2) val.q = 5 ret = self.client.service.echo_extension_class(val) print(ret) self.assertEqual(ret.i, val.i) self.assertEqual(ret.s, val.s) self.assertEqual(ret.f, val.f) self.assertEqual(ret.simple.SimpleClass[0].i, val.simple.SimpleClass[0].i) self.assertEqual(ret.other.dt, val.other.dt) self.assertEqual(ret.p.s, val.p.s) def test_python_exception(self): try: self.client.service.python_exception() raise Exception("must fail") except WebFault as e: pass def test_soap_exception(self): try: self.client.service.soap_exception() raise Exception("must fail") except WebFault as e: pass def test_complex_return(self): ret = self.client.service.complex_return() self.assertEqual(ret.resultCode, 1) self.assertEqual(ret.resultDescription, "Test") self.assertEqual(ret.transactionId, 123) self.assertEqual(ret.roles.RoleEnum[0], "MEMBER") def test_return_invalid_data(self): try: self.client.service.return_invalid_data() raise Exception("must fail") except: pass def test_custom_messages(self): ret = self.client.service.custom_messages("test") assert ret == 'test' def test_echo_simple_bare(self): ret = self.client.service.echo_simple_bare("test") assert ret == 'test' # # This test is disabled because suds does not create the right request # object. Opening the first tag below is wrong. # # # # # # # abc # def # # # # # # The right request looks like this: # # # abc # def # # def _test_echo_complex_bare(self): val = ['abc','def'] ia = self.client.factory.create('stringArray') ia.string.extend(val) ret = self.client.service.echo_complex_bare(ia) assert ret == val if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/test_wsi.py000077500000000000000000000152471417664205300221240ustar00rootroot00000000000000#!/usr/bin/env python # # WS-I interoperability test http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools # latest download: http://www.ws-i.org/Testing/Tools/2005/06/WSI_Test_Java_Final_1.1.zip # # Before launching this test, you should download the zip file and unpack it in this # directory this should create the wsi-test-tools directory. # # Adapted from http://thestewscope.wordpress.com/2008/08/19/ruby-wrapper-for-ws-i-analyzer-tools/ # from Luca Dariz # import os import string from lxml import etree CONFIG_FILE = 'config.xml' SPYNE_TEST_NS = 'spyne.test.interop.server' SPYNE_TEST_PORT = 'Application' SPYNE_REPORT_FILE = 'wsi-report-spyne.xml' WSI_ANALYZER_CONFIG_TEMPLATE=string.Template(""" false ${ASSERTIONS_FILE} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${PORT_NAME} ${WSDL_URI} """) #This must be changed to point to the physical root of the wsi-installation WSI_HOME_TAG = "WSI_HOME" WSI_HOME_VAL = "wsi-test-tools" WSI_JAVA_HOME_TAG = "WSI_JAVA_HOME" WSI_JAVA_HOME_VAL = WSI_HOME_VAL+"/java" WSI_JAVA_OPTS_TAG = "WSI_JAVA_OPTS" WSI_JAVA_OPTS_VAL = " -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser" WSI_TEST_ASSERTIONS_FILE = WSI_HOME_VAL+"/common/profiles/SSBP10_BP11_TAD.xml" WSI_STYLESHEET_FILE = WSI_HOME_VAL+"/common/xsl/report.xsl" WSI_EXECUTION_COMMAND = "java ${WSI_JAVA_OPTS} -Dwsi.home=${WSI_HOME} -cp ${WSI_CP}\ org.wsi.test.analyzer.BasicProfileAnalyzer -config " WSIClasspath=[ WSI_JAVA_HOME_VAL+"/lib/wsi-test-tools.jar", WSI_JAVA_HOME_VAL+"/lib", WSI_JAVA_HOME_VAL+"/lib/xercesImpl.jar", WSI_JAVA_HOME_VAL+"/lib/xmlParserAPIs.jar", WSI_JAVA_HOME_VAL+"/lib/wsdl4j.jar", WSI_JAVA_HOME_VAL+"/lib/uddi4j.jar", WSI_JAVA_HOME_VAL+"/lib/axis.jar", WSI_JAVA_HOME_VAL+"/lib/jaxrpc.jar", WSI_JAVA_HOME_VAL+"/lib/saaj.jar", WSI_JAVA_HOME_VAL+"/lib/commons-discovery.jar", WSI_JAVA_HOME_VAL+"/lib/commons-logging.jar" ] WSI_CLASSPATH_TAG = "WSI_CP" WSI_CLASSPATH_VAL = ':'.join(WSIClasspath) def configure_env(): os.environ[WSI_HOME_TAG] = WSI_HOME_VAL os.environ[WSI_JAVA_HOME_TAG] = WSI_JAVA_HOME_VAL os.environ[WSI_JAVA_OPTS_TAG] = WSI_JAVA_OPTS_VAL os.environ[WSI_CLASSPATH_TAG] = WSI_CLASSPATH_VAL def create_config(wsdl_uri, config_file): print(("Creating config for wsdl at %s ...\n" %wsdl_uri)) # extract target elements service = 'ValidatingApplication' port = 'ValidatingApplication' # for wsdl service declarations: # create config(service, port) vars = {'REPORT_FILE':SPYNE_REPORT_FILE, 'STYLESHEET_FILE':WSI_STYLESHEET_FILE, 'ASSERTIONS_FILE':WSI_TEST_ASSERTIONS_FILE, 'WSDL_NAMESPACE':SPYNE_TEST_NS, 'PORT_NAME':SPYNE_TEST_PORT, 'WSDL_URI':wsdl_uri} config = WSI_ANALYZER_CONFIG_TEMPLATE.substitute(vars) f = open(config_file, 'w') f.write(config) f.close() def analyze_wsdl(config_file): # execute ws-i tests # don't execute Analyzer.sh directly since it needs bash os.system(WSI_EXECUTION_COMMAND + config_file) # parse result e = etree.parse(SPYNE_REPORT_FILE).getroot() summary = etree.ETXPath('{%s}summary' %e.nsmap['wsi-report'])(e) if summary: # retrieve overall result of the test result = summary[0].get('result') if result == 'failed': outs = etree.ETXPath('{%s}artifact' %(e.nsmap['wsi-report'],))(e) # filter for the object describing the wsdl test desc = [o for o in outs if o.get('type') == 'description'][0] # loop over every group test for entry in desc.iterchildren(): # loop over every single test for test in entry.iterchildren(): # simply print the error if there is one # an html can be generated using files in wsi-test-tools/common/xsl if test.get('result') == 'failed': fail_msg = etree.ETXPath('{%s}failureMessage' %e.nsmap['wsi-report'])(test) fail_det = etree.ETXPath('{%s}failureDetail' %e.nsmap['wsi-report'])(test) if fail_msg: print(('\nFAILURE in test %s\n' %test.get('id'))) print((fail_msg[0].text)) if fail_det: print('\nFAILURE MSG\n') print((fail_det[0].text)) from spyne.test.interop._test_soap_client_base import run_server if __name__ == '__main__': run_server('http') configure_env() create_config('http://localhost:9754/?wsdl', CONFIG_FILE) analyze_wsdl(CONFIG_FILE) spyne-spyne-2.14.0/spyne/test/interop/test_zeep.py000077500000000000000000000304071417664205300222600ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging zeep_logger = logging.getLogger('zeep') zeep_logger.setLevel(logging.INFO) import unittest from datetime import datetime from base64 import b64encode, b64decode from spyne.test.interop._test_soap_client_base import server_started from spyne.util import six from zeep import Client from zeep.transports import Transport from zeep.exceptions import Error as ZeepError class TestZeep(unittest.TestCase): def setUp(self): from spyne.test.interop._test_soap_client_base import run_server run_server('http') port, = server_started.keys() transport = Transport(cache=False) self.client = Client("http://localhost:%d/?wsdl" % port, transport=transport) self.ns = "spyne.test.interop.server" def get_inst(self, what): return self.client.get_type(what)() def test_echo_datetime(self): val = datetime.now() ret = self.client.service.echo_datetime(val) assert val == ret def test_echo_datetime_with_invalid_format(self): val = datetime.now() ret = self.client.service.echo_datetime_with_invalid_format(val) assert val == ret def test_echo_date(self): val = datetime.now().date() ret = self.client.service.echo_date(val) assert val == ret def test_echo_date_with_invalid_format(self): val = datetime.now().date() ret = self.client.service.echo_date_with_invalid_format(val) assert val == ret def test_echo_time(self): val = datetime.now().time() ret = self.client.service.echo_time(val) assert val == ret def test_echo_time_with_invalid_format(self): val = datetime.now().time() ret = self.client.service.echo_time_with_invalid_format(val) assert val == ret def test_echo_simple_boolean_array(self): val = [False, False, False, True] ret = self.client.service.echo_simple_boolean_array(val) assert val == ret def test_echo_boolean(self): val = True ret = self.client.service.echo_boolean(val) self.assertEqual(val, ret) val = False ret = self.client.service.echo_boolean(val) self.assertEqual(val, ret) def test_enum(self): val = self.client.get_type("{%s}DaysOfWeekEnum" % self.ns)('Monday') ret = self.client.service.echo_enum(val) assert val == ret def test_bytearray(self): val = b"\x00\x01\x02\x03\x04" ret = self.client.service.echo_bytearray(val) assert val == ret def test_validation(self): non_nillable_class = self.client.get_type("{hunk.sunk}NonNillableClass")() non_nillable_class.i = 6 non_nillable_class.s = None try: self.client.service.non_nillable(non_nillable_class) except ZeepError as e: pass else: raise Exception("must fail") def test_echo_integer_array(self): ia = self.client.get_type('{%s}integerArray' % self.ns)() ia.integer.extend([1, 2, 3, 4, 5]) self.client.service.echo_integer_array(ia) def test_echo_in_header(self): in_header = self.client.get_type('{%s}InHeader' % self.ns)() in_header.s = 'a' in_header.i = 3 ret = self.client.service.echo_in_header(_soapheaders={ 'InHeader': in_header, }) print(ret) out_header = ret.body.echo_in_headerResult self.assertEqual(in_header.s, out_header.s) self.assertEqual(in_header.i, out_header.i) def test_echo_in_complex_header(self): in_header = self.client.get_type('{%s}InHeader' % self.ns)() in_header.s = 'a' in_header.i = 3 in_trace_header = self.client.get_type('{%s}InTraceHeader' % self.ns)() in_trace_header.client = 'suds' in_trace_header.callDate = datetime(year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0) ret = self.client.service.echo_in_complex_header(_soapheaders={ 'InHeader': in_header, 'InTraceHeader': in_trace_header }) print(ret) out_header = ret.body.echo_in_complex_headerResult0 out_trace_header = ret.body.echo_in_complex_headerResult1 self.assertEqual(in_header.s, out_header.s) self.assertEqual(in_header.i, out_header.i) self.assertEqual(in_trace_header.client, out_trace_header.client) self.assertEqual(in_trace_header.callDate, out_trace_header.callDate) def test_send_out_header(self): out_header = self.client.get_type('{%s}OutHeader' % self.ns)() out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 ret = self.client.service.send_out_header() self.assertEqual(ret.header.OutHeader.dt, out_header.dt) self.assertEqual(ret.header.OutHeader.f, out_header.f) def test_send_out_complex_header(self): out_header = self.client.get_type('{%s}OutHeader' % self.ns)() out_header.dt = datetime(year=2000, month=1, day=1) out_header.f = 3.141592653 out_trace_header = self.client.get_type('{%s}OutTraceHeader' % self.ns)() out_trace_header.receiptDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=1) out_trace_header.returnDate = datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1, microsecond=100) ret = self.client.service.send_out_complex_header() self.assertEqual(ret.header.OutHeader.dt, out_header.dt) self.assertEqual(ret.header.OutHeader.f, out_header.f) self.assertEqual(ret.header.OutTraceHeader.receiptDate, out_trace_header.receiptDate) self.assertEqual(ret.header.OutTraceHeader.returnDate, out_trace_header.returnDate) def test_echo_string(self): test_string = "OK" ret = self.client.service.echo_string(test_string) self.assertEqual(ret, test_string) def __get_xml_test_val(self): return { "test_sub": { "test_subsub1": { "test_subsubsub1": ["subsubsub1 value"] }, "test_subsub2": ["subsub2 value 1", "subsub2 value 2"], "test_subsub3": [ { "test_subsub3sub1": ["subsub3sub1 value"] }, { "test_subsub3sub2": ["subsub3sub2 value"] }, ], "test_subsub4": [], "test_subsub5": ["x"], } } def test_echo_simple_class(self): val = self.client.get_type("{%s}SimpleClass" % self.ns)() val.i = 45 val.s = "asd" ret = self.client.service.echo_simple_class(val) assert ret.i == val.i assert ret.s == val.s def test_echo_class_with_self_reference(self): val = self.client.get_type("{%s}ClassWithSelfReference" % self.ns)() val.i = 45 val.sr = self.client.get_type("{%s}ClassWithSelfReference" % self.ns)() val.sr.i = 50 val.sr.sr = None ret = self.client.service.echo_class_with_self_reference(val) assert ret.i == val.i assert ret.sr.i == val.sr.i def test_echo_nested_class(self): val = self.client.get_type("{punk.tunk}NestedClass")() val.i = 45 val.s = "asd" val.f = 12.34 val.ai = self.client.get_type("{%s}integerArray" % self.ns)() val.ai.integer.extend([1, 2, 3, 45, 5, 3, 2, 1, 4]) val.simple = self.client.get_type("{%s}SimpleClassArray" % self.ns)() val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.get_type("{%s}OtherClass" % self.ns)() val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True ret = self.client.service.echo_nested_class(val) self.assertEqual(ret.i, val.i) self.assertEqual(ret.ai.integer, val.ai.integer) self.assertEqual(ret.ai.integer[0], val.ai.integer[0]) self.assertEqual(ret.simple.SimpleClass[0].s, val.simple.SimpleClass[0].s) self.assertEqual(ret.other.dt, val.other.dt) def test_huge_number(self): self.assertEqual(self.client.service.huge_number(), 2 ** int(1e5)) def test_long_string(self): self.assertEqual(self.client.service.long_string(), ('0123456789abcdef' * 16384)) def test_empty(self): self.client.service.test_empty() def test_echo_extension_class(self): val = self.client.get_type("{bar}ExtensionClass")() val.i = 45 val.s = "asd" val.f = 12.34 val.simple = self.client.get_type("{%s}SimpleClassArray" % self.ns)() val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) val.simple.SimpleClass.append(self.client.get_type("{%s}SimpleClass" % self.ns)()) val.simple.SimpleClass[0].i = 45 val.simple.SimpleClass[0].s = "asd" val.simple.SimpleClass[1].i = 12 val.simple.SimpleClass[1].s = "qwe" val.other = self.client.get_type("{%s}OtherClass" % self.ns)() val.other.dt = datetime.now() val.other.d = 123.456 val.other.b = True val.p = self.client.get_type("{hunk.sunk}NonNillableClass")() val.p.dt = datetime(2010, 6, 2) val.p.i = 123 val.p.s = "punk" val.l = datetime(2010, 7, 2) val.q = 5 ret = self.client.service.echo_extension_class(val) print(ret) self.assertEqual(ret.i, val.i) self.assertEqual(ret.s, val.s) self.assertEqual(ret.f, val.f) self.assertEqual(ret.simple.SimpleClass[0].i, val.simple.SimpleClass[0].i) self.assertEqual(ret.other.dt, val.other.dt) self.assertEqual(ret.p.s, val.p.s) def test_python_exception(self): try: self.client.service.python_exception() raise Exception("must fail") except ZeepError as e: pass def test_soap_exception(self): try: self.client.service.soap_exception() raise Exception("must fail") except ZeepError as e: pass def test_complex_return(self): ret = self.client.service.complex_return() self.assertEqual(ret.resultCode, 1) self.assertEqual(ret.resultDescription, "Test") self.assertEqual(ret.transactionId, 123) self.assertEqual(ret.roles.RoleEnum[0], "MEMBER") def test_return_invalid_data(self): try: self.client.service.return_invalid_data() raise Exception("must fail") except: pass def test_custom_messages(self): ret = self.client.service.custom_messages("test") assert ret == 'test' def test_echo_simple_bare(self): ret = self.client.service.echo_simple_bare("test") assert ret == 'test' def test_echo_complex_bare(self): val = ['abc','def'] ret = self.client.service.echo_complex_bare(val) assert ret == val if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/interop/wsi-report-spyne.xml000066400000000000000000001475311417664205300237010ustar00rootroot00000000000000 false wsi-test-tools/common/profiles/SSBP10_BP11_TAD.xml null Application http://localhost:9754/?wsdl spyne-spyne-2.14.0/spyne/test/model/000077500000000000000000000000001417664205300173155ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/model/__init__.py000066400000000000000000000014061417664205300214270ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # spyne-spyne-2.14.0/spyne/test/model/test_binary.py000077500000000000000000000030071417664205300222150ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from lxml import etree from spyne.protocol.soap import Soap11 from spyne.model.binary import ByteArray from spyne.model.binary import _bytes_join import spyne.const.xml ns_xsd = spyne.const.xml.NS_XSD ns_test = 'test_namespace' class TestBinary(unittest.TestCase): def setUp(self): self.data = bytes(bytearray(range(0xff))) def test_data(self): element = etree.Element('test') Soap11().to_parent(None, ByteArray, [self.data], element, ns_test) print(etree.tostring(element, pretty_print=True)) element = element[0] a2 = Soap11().from_element(None, ByteArray, element) self.assertEqual(self.data, _bytes_join(a2)) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/model/test_complex.py000077500000000000000000001005541417664205300224050ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import pytz import datetime import unittest from pprint import pprint from lxml import etree from base64 import b64encode from decimal import Decimal as D from spyne import Application, rpc, mrpc, Service, ByteArray, Array, \ ComplexModel, SelfReference, XmlData, XmlAttribute, Unicode, DateTime, \ Float, Integer, String from spyne.const import xml from spyne.error import ResourceNotFoundError from spyne.interface import Interface from spyne.interface.wsdl import Wsdl11 from spyne.model.addtl import TimeSegment, DateSegment, DateTimeSegment from spyne.protocol import ProtocolBase from spyne.protocol.soap import Soap11 from spyne.server.null import NullServer from spyne.protocol.dictdoc import SimpleDictDocument from spyne.protocol.xml import XmlDocument from spyne.test import FakeApp ns_test = 'test_namespace' class Address(ComplexModel): street = String city = String zip = Integer since = DateTime lattitude = Float longitude = Float Address.resolve_namespace(Address, __name__) class Person(ComplexModel): name = String birthdate = DateTime age = Integer addresses = Array(Address) titles = Array(String) Person.resolve_namespace(Person, __name__) class Employee(Person): employee_id = Integer salary = Float Employee.resolve_namespace(Employee, __name__) class Level2(ComplexModel): arg1 = String arg2 = Float Level2.resolve_namespace(Level2, __name__) class Level3(ComplexModel): arg1 = Integer Level3.resolve_namespace(Level3, __name__) class Level4(ComplexModel): arg1 = String Level4.resolve_namespace(Level4, __name__) class Level1(ComplexModel): level2 = Level2 level3 = Array(Level3) level4 = Array(Level4) Level1.resolve_namespace(Level1, __name__) class TestComplexModel(unittest.TestCase): def test_validate_on_assignment_fail(self): class C(ComplexModel): i = Integer(voa=True) try: C().i = 'a' except ValueError: pass else: raise Exception('must fail with ValueError') def test_validate_on_assignment_success(self): class C(ComplexModel): i = Integer(voa=True) c = C() c.i = None assert c.i is None c.i = 5 assert c.i == 5 def test_simple_class(self): a = Address() a.street = '123 happy way' a.city = 'badtown' a.zip = 32 a.lattitude = 4.3 a.longitude = 88.0 element = etree.Element('test') XmlDocument().to_parent(None, Address, a, element, ns_test) element = element[0] self.assertEqual(5, len(element.getchildren())) a.since = datetime.datetime(year=2011, month=12, day=31, tzinfo=pytz.utc) element = etree.Element('test') XmlDocument().to_parent(None, Address, a, element, ns_test) element = element[0] self.assertEqual(6, len(element.getchildren())) r = XmlDocument().from_element(None, Address, element) self.assertEqual(a.street, r.street) self.assertEqual(a.city, r.city) self.assertEqual(a.zip, r.zip) self.assertEqual(a.lattitude, r.lattitude) self.assertEqual(a.longitude, r.longitude) self.assertEqual(a.since, r.since) def test_nested_class(self): # FIXME: this test is incomplete p = Person() element = etree.Element('test') XmlDocument().to_parent(None, Person, p, element, ns_test) element = element[0] self.assertEqual(None, p.name) self.assertEqual(None, p.birthdate) self.assertEqual(None, p.age) self.assertEqual(None, p.addresses) def test_class_array(self): peeps = [] names = ['bob', 'jim', 'peabody', 'mumblesleeves'] dob = datetime.datetime(1979, 1, 1, tzinfo=pytz.utc) for name in names: a = Person() a.name = name a.birthdate = dob a.age = 27 peeps.append(a) type = Array(Person) type.resolve_namespace(type, __name__) element = etree.Element('test') XmlDocument().to_parent(None, type, peeps, element, ns_test) element = element[0] self.assertEqual(4, len(element.getchildren())) peeps2 = XmlDocument().from_element(None, type, element) for i in range(0, 4): self.assertEqual(peeps2[i].name, names[i]) self.assertEqual(peeps2[i].birthdate, dob) def test_class_nested_array(self): peeps = [] names = ['bob', 'jim', 'peabody', 'mumblesleves'] for name in names: a = Person() a.name = name a.birthdate = datetime.datetime(1979, 1, 1) a.age = 27 a.addresses = [] for i in range(0, 25): addr = Address() addr.street = '555 downtown' addr.city = 'funkytown' a.addresses.append(addr) peeps.append(a) arr = Array(Person) arr.resolve_namespace(arr, __name__) element = etree.Element('test') XmlDocument().to_parent(None, arr, peeps, element, ns_test) element = element[0] self.assertEqual(4, len(element.getchildren())) peeps2 = XmlDocument().from_element(None, arr, element) for peep in peeps2: self.assertEqual(27, peep.age) self.assertEqual(25, len(peep.addresses)) self.assertEqual('funkytown', peep.addresses[18].city) def test_complex_class(self): l = Level1() l.level2 = Level2() l.level2.arg1 = 'abcd' l.level2.arg2 = 1.444 l.level3 = [] l.level4 = [] for i in range(0, 100): a = Level3() a.arg1 = i l.level3.append(a) for i in range(0, 4): a = Level4() a.arg1 = str(i) l.level4.append(a) element = etree.Element('test') XmlDocument().to_parent(None, Level1, l, element, ns_test) element = element[0] l1 = XmlDocument().from_element(None, Level1, element) self.assertEqual(l1.level2.arg1, l.level2.arg1) self.assertEqual(l1.level2.arg2, l.level2.arg2) self.assertEqual(len(l1.level4), len(l.level4)) self.assertEqual(100, len(l.level3)) class X(ComplexModel): __namespace__ = 'tns' x = Integer(nillable=True, max_occurs='unbounded') class Y(X): __namespace__ = 'tns' y = Integer class TestIncompleteInput(unittest.TestCase): def test_x(self): x = X() x.x = [1, 2] element = etree.Element('test') XmlDocument().to_parent(None, X, x, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, X, msg) self.assertEqual(r.x, [1, 2]) def test_y_fromxml(self): x = X() x.x = [1, 2] element = etree.Element('test') XmlDocument().to_parent(None, X, x, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, Y, msg) self.assertEqual(r.x, [1, 2]) def test_y_toxml(self): y = Y() y.x = [1, 2] y.y = 38 element = etree.Element('test') XmlDocument().to_parent(None, Y, y, element, 'tns') msg = element[0] r = XmlDocument().from_element(None, Y, msg) def test_serialization_instance_on_subclass(self): test_values = { 'x': [1, 2], 'y': 38 } instance = Y.get_serialization_instance(test_values) self.assertEqual(instance.x, [1, 2]) self.assertEqual(instance.y, 38) class SisMsg(ComplexModel): data_source = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) direction = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) interface_name = String(nillable=False, min_occurs=1, max_occurs=1, max_len=50) crt_dt = DateTime(nillable=False) class EncExtractXs(ComplexModel): __min_occurs__ = 1 __max_occurs__ = 1 mbr_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) enc_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) hist_idn = Integer(nillable=False, min_occurs=1, max_occurs=1, max_len=18) class TestComplex(unittest.TestCase): def test_array_type_name(self): assert Array(String, type_name='punk').__type_name__ == 'punk' def test_ctor_kwargs(self): class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) children = Array(Unicode) v = Category(id=5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] def test_ctor_args(self): class Category(ComplexModel): id = XmlData(Integer(min_occurs=1, max_occurs=1, nillable=False)) children = Array(Unicode) v = Category(id=5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] v = Category(5, children=['a','b']) assert v.id == 5 assert v.children == ['a', 'b'] def test_ctor_args_2(self): class Category(ComplexModel): children = Array(Unicode) class BetterCategory(Category): sub_category = Unicode v = BetterCategory(children=['a','b'], sub_category='aaa') assert v.children == ['a', 'b'] assert v.sub_category == 'aaa' def test_flat_type_info(self): class A(ComplexModel): i = Integer class B(A): s = String assert 's' in B.get_flat_type_info(B) assert 'i' in B.get_flat_type_info(B) def test_flat_type_info_attr(self): class A(ComplexModel): i = Integer ia = XmlAttribute(Integer) class B(A): s = String sa = XmlAttribute(String) assert 's' in B.get_flat_type_info(B) assert 'i' in B.get_flat_type_info(B) assert 'sa' in B.get_flat_type_info(B) assert 'ia' in B.get_flat_type_info(B) assert 'sa' in B.get_flat_type_info(B).attrs assert 'ia' in B.get_flat_type_info(B).attrs class TestXmlAttribute(unittest.TestCase): def assertIsNotNone(self, obj, msg=None): """Stolen from Python 2.7 stdlib.""" if obj is None: standardMsg = 'unexpectedly None' self.fail(self._formatMessage(msg, standardMsg)) def test_add_to_schema(self): class CM(ComplexModel): i = Integer s = String a = XmlAttribute(String) app = FakeApp() app.tns = 'tns' CM.resolve_namespace(CM, app.tns) interface = Interface(app) interface.add_class(CM) wsdl = Wsdl11(interface) wsdl.build_interface_document('http://a-aaaa.com') pref = CM.get_namespace_prefix(interface) type_def = wsdl.get_schema_info(pref).types[CM.get_type_name()] attribute_def = type_def.find(xml.XSD('attribute')) print(etree.tostring(type_def, pretty_print=True)) self.assertIsNotNone(attribute_def) self.assertEqual(attribute_def.get('name'), 'a') self.assertEqual(attribute_def.get('type'), CM.a.type.get_type_name_ns(interface)) def test_b64_non_attribute(self): class PacketNonAttribute(ComplexModel): __namespace__ = 'myns' Data = ByteArray test_string = b'yo test data' b64string = b64encode(test_string) gg = PacketNonAttribute(Data=[test_string]) element = etree.Element('test') Soap11().to_parent(None, PacketNonAttribute, gg, element, gg.get_namespace()) element = element[0] #print etree.tostring(element, pretty_print=True) data = element.find('{%s}Data' % gg.get_namespace()).text self.assertEqual(data, b64string.decode('ascii')) s1 = Soap11().from_element(None, PacketNonAttribute, element) assert s1.Data[0] == test_string def test_b64_attribute(self): class PacketAttribute(ComplexModel): __namespace__ = 'myns' Data = XmlAttribute(ByteArray, use='required') test_string = b'yo test data' b64string = b64encode(test_string) gg = PacketAttribute(Data=[test_string]) element = etree.Element('test') Soap11().to_parent(None, PacketAttribute, gg, element, gg.get_namespace()) element = element[0] print(etree.tostring(element, pretty_print=True)) print(element.attrib) self.assertEqual(element.attrib['Data'], b64string.decode('ascii')) s1 = Soap11().from_element(None, PacketAttribute, element) assert s1.Data[0] == test_string def test_customized_type(self): class SomeClass(ComplexModel): a = XmlAttribute(Integer(ge=4)) class SomeService(Service): @rpc(SomeClass) def some_call(ctx, some_class): pass app = Application([SomeService], 'some_tns') class TestSimpleTypeRestrictions(unittest.TestCase): def test_simple_type_info(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String sti = CCM.get_simple_type_info(CCM) pprint(sti) assert "i" in sti assert sti["i"].path == ('i',) assert sti["i"].type is Integer assert sti["s"].parent is CCM assert "s" in sti assert sti["s"].path == ('s',) assert sti["s"].type is String assert sti["s"].parent is CCM assert "c.i" in sti assert sti["c.i"].path == ('c','i') assert sti["c.i"].type is Integer assert sti["c.i"].parent is CM assert "c.s" in sti assert sti["c.s"].path == ('c','s') assert sti["c.s"].type is String assert sti["c.s"].parent is CM def test_simple_type_info_conflicts(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM c_i = Float try: CCM.get_simple_type_info(CCM, hier_delim='_') except ValueError: pass else: raise Exception("must fail") class TestFlatDict(unittest.TestCase): def test_basic(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String val = CCM(i=5, s='a', c=CM(i=7, s='b')) d = SimpleDictDocument().object_to_simple_dict(CCM, val) assert d['i'] == 5 assert d['s'] == 'a' assert d['c.i'] == 7 assert d['c.s'] == 'b' assert len(d) == 4 def test_sub_name_ser(self): class CM(ComplexModel): integer = Integer(sub_name='i') string = String(sub_name='s') val = CM(integer=7, string='b') d = SimpleDictDocument().object_to_simple_dict(CM, val) pprint(d) assert d['i'] == 7 assert d['s'] == 'b' assert len(d) == 2 def test_sub_name_deser(self): class CM(ComplexModel): integer = Integer(sub_name='i') string = String(sub_name='s') d = {'i': [7], 's': ['b']} val = SimpleDictDocument().simple_dict_to_object(None, d, CM) pprint(d) assert val.integer == 7 assert val.string == 'b' def test_array_not_none(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = Array(CM) val = CCM(c=[CM(i=i, s='b'*(i+1)) for i in range(2)]) d = SimpleDictDocument().object_to_simple_dict(CCM, val) print(d) assert d['c[0].i'] == 0 assert d['c[0].s'] == 'b' assert d['c[1].i'] == 1 assert d['c[1].s'] == 'bb' assert len(d) == 4 def test_array_none(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = Array(CM) val = CCM() d = SimpleDictDocument().object_to_simple_dict(CCM, val) print(d) assert len(d) == 0 def test_array_nested(self): class CM(ComplexModel): i = Array(Integer) class CCM(ComplexModel): c = Array(CM) val = CCM(c=[CM(i=range(i)) for i in range(2, 4)]) d = SimpleDictDocument().object_to_simple_dict(CCM, val) pprint(d) assert d['c[0].i'] == [0, 1] assert d['c[1].i'] == [0, 1, 2] assert len(d) == 2 def test_array_nonwrapped(self): i = Array(Integer, wrapped=False) assert issubclass(i, Integer), i assert i.Attributes.max_occurs == D('infinity') class TestSelfRefence(unittest.TestCase): def test_canonical_case(self): class TestSelfReference(ComplexModel): self_reference = SelfReference c = TestSelfReference._type_info['self_reference'] c = c.__orig__ or c assert c is TestSelfReference class SoapService(Service): @rpc(_returns=TestSelfReference) def view_categories(ctx): pass Application([SoapService], 'service.soap') def test_self_referential_array_workaround(self): from spyne.util.dictdoc import get_object_as_dict class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) Category._type_info['children'] = Array(Category) parent = Category() parent.children = [Category(id=0), Category(id=1)] d = get_object_as_dict(parent, Category) pprint(d) assert d['children'][0]['id'] == 0 assert d['children'][1]['id'] == 1 class SoapService(Service): @rpc(_returns=Category) def view_categories(ctx): pass Application([SoapService], 'service.soap', in_protocol=ProtocolBase(), out_protocol=ProtocolBase()) def test_canonical_array(self): class Category(ComplexModel): id = Integer(min_occurs=1, max_occurs=1, nillable=False) children = Array(SelfReference) parent = Category() parent.children = [Category(id=1), Category(id=2)] sr, = Category._type_info['children']._type_info.values() assert issubclass(sr, Category) class TestMemberRpc(unittest.TestCase): def test_simple(self): class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_simple_customize(self): class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.customize(zart='zurt').Attributes.methods print(methods) assert 'put' in methods def test_simple_with_fields(self): class SomeComplexModel(ComplexModel): a = Integer @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_simple_with_explicit_fields(self): class SomeComplexModel(ComplexModel): _type_info = [('a', Integer)] @mrpc() def put(self, ctx): return "PUNK!!!" methods = SomeComplexModel.Attributes.methods print(methods) assert 'put' in methods def test_native_call(self): v = 'whatever' class SomeComplexModel(ComplexModel): @mrpc() def put(self, ctx): return v assert SomeComplexModel().put(None) == v def test_interface(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods print(methods) assert 'member_method' in methods class SomeService(Service): @rpc(_returns=SomeComplexModel) def service_method(ctx): return SomeComplexModel() app = Application([SomeService], 'some_ns') mmm = __name__ + '.SomeComplexModel.member_method' assert mmm in app.interface.method_id_map def test_interface_mult(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods print(methods) assert 'member_method' in methods class SomeService(Service): @rpc(_returns=SomeComplexModel) def service_method(ctx): return SomeComplexModel() @rpc(_returns=SomeComplexModel.customize(type_name='zon')) def service_method_2(ctx): return SomeComplexModel() app = Application([SomeService], 'some_ns') mmm = __name__ + '.SomeComplexModel.member_method' assert mmm in app.interface.method_id_map def test_remote_call_error(self): from spyne import mrpc v = 'deger' class SomeComplexModel(ComplexModel): @mrpc(_returns=SelfReference) def put(self, ctx): return v class SomeService(Service): @rpc(_returns=SomeComplexModel) def get(ctx): return SomeComplexModel() null = NullServer(Application([SomeService], tns='some_tns')) try: null.service.put() except ResourceNotFoundError: pass else: raise Exception("Must fail with: \"Requested resource " "'{spyne.test.model.test_complex}SomeComplexModel' not found\"") def test_signature(self): class SomeComplexModel(ComplexModel): @mrpc() def member_method(self, ctx): pass methods = SomeComplexModel.Attributes.methods # we use __orig__ because implicit classes are .customize(validate_freq=False)'d assert methods['member_method'].in_message._type_info[0].__orig__ is SomeComplexModel def test_self_reference(self): from spyne import mrpc class SomeComplexModel(ComplexModel): @mrpc(_returns=SelfReference) def method(self, ctx): pass methods = SomeComplexModel.Attributes.methods assert methods['method'].out_message._type_info[0] is SomeComplexModel def test_remote_call_success(self): from spyne import mrpc class SomeComplexModel(ComplexModel): i = Integer @mrpc(_returns=SelfReference) def echo(self, ctx): return self class SomeService(Service): @rpc(_returns=SomeComplexModel) def get(ctx): return SomeComplexModel() null = NullServer(Application([SomeService], tns='some_tns')) v = SomeComplexModel(i=5) assert null.service['SomeComplexModel.echo'](v) is v def test_order(self): class CM(ComplexModel): _type_info = [ ('a', Integer), ('c', Integer(order=0)) ] assert CM._type_info.keys() == ['c', 'a'] class TestDoc(unittest.TestCase): def test_parent_doc(self): class SomeComplexModel(ComplexModel): """Some docstring""" some_field = Unicode class Annotations(ComplexModel.Annotations): __use_parent_doc__ = True assert "Some docstring" == SomeComplexModel.get_documentation() def test_annotation(self): class SomeComplexModel(ComplexModel): """Some docstring""" class Annotations(ComplexModel.Annotations): doc = "Some annotations" some_field = Unicode assert "Some annotations" == SomeComplexModel.get_documentation() def test_no_parent_doc(self): class SomeComplexModel(ComplexModel): """Some docstring""" class Annotations(ComplexModel.Annotations): __use_parent_doc__ = False some_field = Unicode assert "" == SomeComplexModel.get_documentation() def test_parent_doc_customize(self): """Check that we keep the documentation when we use customize""" class SomeComplexModel(ComplexModel): """Some docstring""" some_field = Unicode class Annotations(ComplexModel.Annotations): __use_parent_doc__ = True assert "Some docstring" == SomeComplexModel.customize().get_documentation() class TestCustomize(unittest.TestCase): def test_base_class(self): class A(ComplexModel): s = Unicode assert A.customize().__extends__ is None class B(A): i = Integer assert B.__orig__ is None B2 = B.customize() assert B2.__orig__ is B assert B2.__extends__ is A B3 = B2.customize() assert B3.__orig__ is B assert B3.__extends__ is A def test_noop(self): class A(ComplexModel): s = Unicode assert A.get_flat_type_info(A)['s'].Attributes.max_len == D('inf') def test_cust_simple(self): # simple types are different from complex ones for __extends__ handling. # simple types set __orig__ and __extends__ on customization. # complex types set __orig__ but not extend. # for complex types, __extend__ is set only on explicit inheritance t = Unicode(max_len=10) assert t.Attributes.max_len == 10 assert t.__extends__ is Unicode assert t.__orig__ is Unicode def test_cust_simple_again(self): t = Unicode(max_len=10) t2 = t(min_len=5) assert t2.Attributes.max_len == 10 assert t2.Attributes.min_len == 5 assert t2.__extends__ is t assert t2.__orig__ is Unicode def test_cust_complex(self): class A(ComplexModel): s = Unicode A2 = A.customize( child_attrs=dict( s=dict( max_len=10 ) ) ) assert A2.get_flat_type_info(A2)['s'].Attributes.max_len == 10 def test_cust_base_class(self): class A(ComplexModel): s = Unicode class B(A): i = Integer B2 = B.customize( child_attrs=dict( s=dict( max_len=10, ), ), ) assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 def test_cust_again_base_class(self): class A(ComplexModel): s = Unicode A2 = A.customize() try: class B(A2): i = Integer except AssertionError: pass else: raise Exception("must fail") def test_cust_array(self): A = Array(Unicode) assert A.__orig__ is Array assert A.__extends__ is None assert issubclass(A, Array) def test_cust_array_again(self): A = Array(Unicode) A = A.customize(foo='bar') assert A.Attributes.foo == 'bar' assert A.__orig__ is Array assert A.__extends__ is None assert issubclass(A, Array) def test_cust_array_serializer(self): A = Array(Unicode) A = A.customize( serializer_attrs=dict( max_len=10, ), ) serializer, = A._type_info.values() assert serializer.Attributes.max_len == 10 assert serializer.__orig__ is Unicode assert issubclass(serializer, Unicode) def test_cust_sub_array(self): """vanilla class is passed as base""" class A(ComplexModel): s = Array(Unicode) d = dict( child_attrs=dict( s=dict( serializer_attrs=dict( max_len=10, ), ), ), ) A2 = A.customize(**d) ser, = A2._type_info['s']._type_info.values() assert ser.Attributes.max_len == 10 class B(A): i = Integer B2 = B.customize(**d) b2_fti = B2.get_flat_type_info(B2) ser, = b2_fti['s']._type_info.values() assert ser.Attributes.max_len == 10 def test_cust_side_effect(self): class A(ComplexModel): s = Unicode i = Integer class B(A): d = DateTime B2 = B.customize(child_attrs=dict(s=dict(max_len=10))) assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 B3 = B2.customize(child_attrs=dict(d=dict(dt_format="%y"))) assert B3.get_flat_type_info(B3)['s'].Attributes.max_len == 10 def test_cust_all(self): class A(ComplexModel): s = Unicode i = Unicode class B(A): d = DateTime B2 = B.customize(child_attrs_all=dict(max_len=10)) assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 assert B2.get_flat_type_info(B2)['i'].Attributes.max_len == 10 def test_cust_noexc(self): class A(ComplexModel): s = Unicode i = Integer class B(A): d = DateTime B2 = B.customize(child_attrs_noexc=dict(s=dict(max_len=10))) assert B2.get_flat_type_info(B2)['s'].Attributes.max_len == 10 assert B2.get_flat_type_info(B2)['s'].Attributes.exc == False assert B2.get_flat_type_info(B2)['i'].Attributes.exc == True def test_complex_type_name_clashes(self): class TestComplexModel(ComplexModel): attr1 = String TestComplexModel1 = TestComplexModel class TestComplexModel(ComplexModel): attr2 = String TestComplexModel2 = TestComplexModel class TestService(Service): @rpc(TestComplexModel1) def test1(ctx, obj): pass @rpc(TestComplexModel2) def test2(ctx, obj): pass try: Application([TestService], 'tns') except Exception as e: print(e) else: raise Exception("must fail with: " "ValueError: classes " " " "and " " " "have conflicting names.") class TestAdditional(unittest.TestCase): def test_time_segment(self): data = TimeSegment.from_string("[11:12:13.123456,14:15:16.789012]") assert data.start_inclusive assert data.start == datetime.time(11, 12, 13, 123456) assert data.end == datetime.time(14, 15, 16, 789012) assert data.end_inclusive def test_date_segment(self): data = DateSegment.from_string("[2016-03-03,2016-05-07[") assert data.start_inclusive == True assert data.start == datetime.date(2016, 3, 3) assert data.end == datetime.date(2016, 5, 7) assert data.end_inclusive == False def test_datetime_segment(self): data = DateTimeSegment.from_string("]2016-03-03T10:20:30.405060," "2016-05-07T00:01:02.030405]") assert data.start_inclusive == False assert data.start == datetime.datetime(2016, 3, 3, 10, 20, 30, 405060) assert data.end == datetime.datetime(2016, 5, 7, 0, 1, 2, 30405) assert data.end_inclusive == True if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/model/test_enum.py000077500000000000000000000120611417664205300216750ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from pprint import pprint from spyne.application import Application from spyne.const.xml import XSD from spyne.interface.wsdl.wsdl11 import Wsdl11 from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.protocol.xml import XmlDocument from spyne.protocol.soap.soap11 import Soap11 from spyne.server.wsgi import WsgiApplication from spyne.service import Service from spyne.decorator import rpc from spyne.model.enum import Enum from lxml import etree vals = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ] DaysOfWeekEnum = Enum( 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', type_name = 'DaysOfWeekEnum', ) class SomeService(Service): @rpc(DaysOfWeekEnum, _returns=DaysOfWeekEnum) def get_the_day(self, day): return DaysOfWeekEnum.Sunday class SomeClass(ComplexModel): days = DaysOfWeekEnum(max_occurs=7) class TestEnum(unittest.TestCase): def setUp(self): self.app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'test' self.server = WsgiApplication(self.app) self.wsdl = Wsdl11(self.app.interface) self.wsdl.build_interface_document('prot://url') def test_wsdl(self): wsdl = self.wsdl.get_interface_document() elt = etree.fromstring(wsdl) simple_type = elt.xpath('//xs:simpleType', namespaces=self.app.interface.nsmap)[0] print((etree.tostring(elt, pretty_print=True))) print(simple_type) self.assertEqual(simple_type.attrib['name'], 'DaysOfWeekEnum') self.assertEqual(simple_type[0].tag, XSD("restriction")) self.assertEqual([e.attrib['value'] for e in simple_type[0]], vals) def test_serialize(self): mo = DaysOfWeekEnum.Monday print((repr(mo))) elt = etree.Element('test') XmlDocument().to_parent(None, DaysOfWeekEnum, mo, elt, 'test_namespace') elt = elt[0] ret = XmlDocument().from_element(None, DaysOfWeekEnum, elt) self.assertEqual(mo, ret) def test_serialize_complex_array(self): days = [ DaysOfWeekEnum.Monday, DaysOfWeekEnum.Tuesday, DaysOfWeekEnum.Wednesday, DaysOfWeekEnum.Thursday, DaysOfWeekEnum.Friday, DaysOfWeekEnum.Saturday, DaysOfWeekEnum.Sunday, ] days_xml = [ ('{tns}DaysOfWeekEnum', 'Monday'), ('{tns}DaysOfWeekEnum', 'Tuesday'), ('{tns}DaysOfWeekEnum', 'Wednesday'), ('{tns}DaysOfWeekEnum', 'Thursday'), ('{tns}DaysOfWeekEnum', 'Friday'), ('{tns}DaysOfWeekEnum', 'Saturday'), ('{tns}DaysOfWeekEnum', 'Sunday'), ] DaysOfWeekEnumArray = Array(DaysOfWeekEnum) DaysOfWeekEnumArray.__namespace__ = 'tns' elt = etree.Element('test') XmlDocument().to_parent(None, DaysOfWeekEnumArray, days, elt, 'test_namespace') elt = elt[0] ret = XmlDocument().from_element(None, Array(DaysOfWeekEnum), elt) assert days == ret print((etree.tostring(elt, pretty_print=True))) pprint(self.app.interface.nsmap) assert days_xml == [ (e.tag, e.text) for e in elt.xpath('//tns:DaysOfWeekEnum', namespaces=self.app.interface.nsmap)] def test_serialize_simple_array(self): t = SomeClass(days=[ DaysOfWeekEnum.Monday, DaysOfWeekEnum.Tuesday, DaysOfWeekEnum.Wednesday, DaysOfWeekEnum.Thursday, DaysOfWeekEnum.Friday, DaysOfWeekEnum.Saturday, DaysOfWeekEnum.Sunday, ]) SomeClass.resolve_namespace(SomeClass, 'tns') elt = etree.Element('test') XmlDocument().to_parent(None, SomeClass, t, elt, 'test_namespace') elt = elt[0] print((etree.tostring(elt, pretty_print=True))) ret = XmlDocument().from_element(None, SomeClass, elt) self.assertEqual(t.days, ret.days) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/model/test_exception.py000077500000000000000000000155131417664205300227340ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.test import FakeApp from spyne.interface import Interface from spyne.interface.wsdl import Wsdl11 from spyne.protocol.xml import XmlDocument from spyne.model.fault import Fault class FaultTests(unittest.TestCase): def test_ctor_defaults(self): fault = Fault() self.assertEqual(fault.faultcode, 'Server') self.assertEqual(fault.faultstring, 'Fault') self.assertEqual(fault.faultactor, '') self.assertEqual(fault.detail, None) self.assertEqual(repr(fault), "Fault(Server: 'Fault')") def test_ctor_faultcode_w_senv_prefix(self): fault = Fault(faultcode='Other') self.assertEqual(fault.faultcode, 'Other') self.assertEqual(repr(fault), "Fault(Other: 'Fault')") def test_ctor_explicit_faultstring(self): fault = Fault(faultstring='Testing') self.assertEqual(fault.faultstring, 'Testing') self.assertEqual(repr(fault), "Fault(Server: 'Testing')") def test_to_parent_wo_detail(self): from lxml.etree import Element import spyne.const.xml ns_soap_env = spyne.const.xml.NS_SOAP11_ENV soap_env = spyne.const.xml.PREFMAP[spyne.const.xml.NS_SOAP11_ENV] element = Element('testing') fault = Fault() cls = Fault XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') (child,) = element.getchildren() self.assertEqual(child.tag, '{%s}Fault' % ns_soap_env) self.assertEqual(child.find('faultcode').text, '%s:Server' % soap_env) self.assertEqual(child.find('faultstring').text, 'Fault') self.assertEqual(child.find('faultactor').text, '') self.assertFalse(child.findall('detail')) def test_to_parent_w_detail(self): from lxml.etree import Element element = Element('testing') detail = Element('something') fault = Fault(detail=detail) cls = Fault XmlDocument().to_parent(None, cls, fault, element, 'urn:ignored') (child,) = element.getchildren() self.assertTrue(child.find('detail').find('something') is detail) def test_from_xml_wo_detail(self): from lxml.etree import Element from lxml.etree import SubElement from spyne.const.xml import PREFMAP, SOAP11_ENV, NS_SOAP11_ENV soap_env = PREFMAP[NS_SOAP11_ENV] element = Element(SOAP11_ENV('Fault')) fcode = SubElement(element, 'faultcode') fcode.text = '%s:other' % soap_env fstr = SubElement(element, 'faultstring') fstr.text = 'Testing' actor = SubElement(element, 'faultactor') actor.text = 'phreddy' fault = XmlDocument().from_element(None, Fault, element) self.assertEqual(fault.faultcode, '%s:other' % soap_env) self.assertEqual(fault.faultstring, 'Testing') self.assertEqual(fault.faultactor, 'phreddy') self.assertEqual(fault.detail, None) def test_from_xml_w_detail(self): from lxml.etree import Element from lxml.etree import SubElement from spyne.const.xml import SOAP11_ENV element = Element(SOAP11_ENV('Fault')) fcode = SubElement(element, 'faultcode') fcode.text = 'soap11env:other' fstr = SubElement(element, 'faultstring') fstr.text = 'Testing' actor = SubElement(element, 'faultactor') actor.text = 'phreddy' detail = SubElement(element, 'detail') fault = XmlDocument().from_element(None, Fault, element) self.assertTrue(fault.detail is detail) def test_add_to_schema_no_extends(self): from spyne.const.xml import XSD class cls(Fault): __namespace__='ns' @classmethod def get_type_name_ns(self, app): return 'testing:My' interface = Interface(FakeApp()) interface.add_class(cls) pref = cls.get_namespace_prefix(interface) wsdl = Wsdl11(interface) wsdl.build_interface_document('prot://addr') schema = wsdl.get_schema_info(pref) self.assertEqual(len(schema.types), 1) c_cls = interface.classes['{ns}cls'] c_elt = schema.types[0] self.assertTrue(c_cls is cls) self.assertEqual(c_elt.tag, XSD('complexType')) self.assertEqual(c_elt.get('name'), 'cls') self.assertEqual(len(schema.elements), 1) e_elt = schema.elements.values()[0] self.assertEqual(e_elt.tag, XSD('element')) self.assertEqual(e_elt.get('name'), 'cls') self.assertEqual(e_elt.get('type'), 'testing:My') self.assertEqual(len(e_elt), 0) def test_add_to_schema_w_extends(self): from spyne.const.xml import XSD class base(Fault): __namespace__ = 'ns' @classmethod def get_type_name_ns(self, app): return 'testing:Base' class cls(Fault): __namespace__ = 'ns' @classmethod def get_type_name_ns(self, app): return 'testing:My' interface = Interface(FakeApp()) interface.add_class(cls) pref = cls.get_namespace_prefix(interface) wsdl = Wsdl11(interface) wsdl.build_interface_document('prot://addr') schema = wsdl.get_schema_info(pref) self.assertEqual(len(schema.types), 1) self.assertEqual(len(interface.classes), 1) c_cls = next(iter(interface.classes.values())) c_elt = next(iter(schema.types.values())) self.assertTrue(c_cls is cls) self.assertEqual(c_elt.tag, XSD('complexType')) self.assertEqual(c_elt.get('name'), 'cls') from lxml import etree print(etree.tostring(c_elt, pretty_print=True)) self.assertEqual(len(c_elt), 0) class DummySchemaEntries: def __init__(self, app): self.app = app self._complex_types = [] self._elements = [] def add_complex_type(self, cls, ct): self._complex_types.append((cls, ct)) def add_element(self, cls, elt): self._elements.append((cls, elt)) if __name__ == '__main__': #pragma NO COVERAGE unittest.main() spyne-spyne-2.14.0/spyne/test/model/test_primitive.py000077500000000000000000001112631417664205300227450ustar00rootroot00000000000000#!/usr/bin/env python # coding=utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import re import uuid import datetime import unittest import warnings import pytz import spyne from datetime import timedelta from lxml import etree from spyne.model.primitive._base import re_match_with_span as rmws from spyne.util import six from spyne.const import xml as ns from spyne import Null, AnyDict, Uuid, Array, ComplexModel, Date, Time, \ Boolean, DateTime, Duration, Float, Integer, NumberLimitsWarning, Unicode, \ String, Decimal, Integer16, ModelBase, MimeType, MimeTypeStrict, MediaType from spyne.protocol import ProtocolBase from spyne.protocol.xml import XmlDocument ns_test = 'test_namespace' class TestCast(unittest.TestCase): pass # TODO: test Unicode(cast=str) class TestPrimitive(unittest.TestCase): def test_mime_type_family(self): mime_attr = MimeType.Attributes mime_strict_attr = MimeTypeStrict.Attributes assert rmws(mime_attr, u'application/foo') assert not rmws(mime_attr, u'application/ foo') assert not rmws(mime_attr, u'application/') assert rmws(mime_attr, u'foo/bar') assert not rmws(mime_attr, u'foo/bar ') assert not rmws(mime_strict_attr, u'foo/bar') media_attr = MediaType.Attributes media_strict_attr = MediaType.Attributes print(media_attr.pattern) assert rmws(media_attr, u'text/plain') assert not rmws(media_attr, u' text/plain') assert rmws(media_attr, u'text/plain;') assert rmws(media_attr, u'text/plain;charset=utf-8') assert rmws(media_attr, u'text/plain; charset="utf-8"') assert rmws(media_attr, u'text/plain; charset=utf-8') assert rmws(media_attr, u'text/plain; charset=utf-8 ') assert rmws(media_attr, u'text/plain; charset=utf-8;') assert rmws(media_attr, u'text/plain; charset=utf-8; ') assert not rmws(media_attr, u'text/plain; charset=utf-8; foo') assert not rmws(media_attr, u'text/plain; charset=utf-8; foo=') assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""') assert rmws(media_attr, u'text/plain; charset=utf-8; foo="";') assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""; ') assert rmws(media_attr, u'text/plain; charset=utf-8; foo=""; ') assert not rmws(media_attr, u'text/plain;; charset=utf-8; foo=""') assert not rmws(media_attr, u'text/plain;;; charset=utf-8; foo=""') assert not rmws(media_attr, u'text/plain; charset=utf-8;; foo=""') assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""') assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo="";') assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""; ; ') assert not rmws(media_strict_attr, u' applicaton/json;') assert MediaType def test_getitem_cust(self): assert Unicode[dict(max_len=2)].Attributes.max_len def test_ancestors(self): class A(ComplexModel): i = Integer class B(A): i2 = Integer class C(B): i3 = Integer assert C.ancestors() == [B, A] assert B.ancestors() == [A] assert A.ancestors() == [] def test_nillable_quirks(self): assert ModelBase.Attributes.nillable == True class Attributes(ModelBase.Attributes): nillable = False nullable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nillable = True assert Attributes.nillable == True assert Attributes.nullable == True class Attributes(ModelBase.Attributes): nillable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nullable = True assert Attributes.nillable == True assert Attributes.nullable == True class Attributes(ModelBase.Attributes): nullable = False assert Attributes.nillable == False assert Attributes.nullable == False class Attributes(ModelBase.Attributes): nullable = False class Attributes(Attributes): pass assert Attributes.nullable == False def test_nillable_inheritance_quirks(self): class Attributes(ModelBase.Attributes): nullable = False class AttrMixin: pass class NewAttributes(Attributes, AttrMixin): pass assert NewAttributes.nullable is False class AttrMixin: pass class NewAttributes(AttrMixin, Attributes): pass assert NewAttributes.nullable is False def test_decimal(self): assert Decimal(10, 4).Attributes.total_digits == 10 assert Decimal(10, 4).Attributes.fraction_digits == 4 def test_decimal_format(self): f = 123456 str_format = '${0}' element = etree.Element('test') XmlDocument().to_parent(None, Decimal(str_format=str_format), f, element, ns_test) element = element[0] self.assertEqual(element.text, '$123456') def test_string(self): s = String() element = etree.Element('test') XmlDocument().to_parent(None, String, 'value', element, ns_test) element = element[0] self.assertEqual(element.text, 'value') value = XmlDocument().from_element(None, String, element) self.assertEqual(value, 'value') def test_datetime(self): n = datetime.datetime.now(pytz.utc) element = etree.Element('test') XmlDocument().to_parent(None, DateTime, n, element, ns_test) element = element[0] self.assertEqual(element.text, n.isoformat()) dt = XmlDocument().from_element(None, DateTime, element) self.assertEqual(n, dt) def test_datetime_format(self): n = datetime.datetime.now().replace(microsecond=0) format = "%Y %m %d %H %M %S" element = etree.Element('test') XmlDocument().to_parent(None, DateTime(dt_format=format), n, element, ns_test) element = element[0] assert element.text == datetime.datetime.strftime(n, format) dt = XmlDocument().from_element(None, DateTime(dt_format=format), element) assert n == dt def test_datetime_unicode_format(self): n = datetime.datetime.now().replace(microsecond=0) format = u"%Y %m %d\u00a0%H %M %S" element = etree.Element('test') XmlDocument().to_parent(None, DateTime(dt_format=format), n, element, ns_test) element = element[0] if six.PY2: assert element.text == n.strftime(format.encode('utf8')) \ .decode('utf8') else: assert element.text == n.strftime(format) dt = XmlDocument().from_element(None, DateTime(dt_format=format), element) assert n == dt def test_date_format(self): t = datetime.date.today() format = "%Y-%m-%d" element = etree.Element('test') XmlDocument().to_parent(None, Date(date_format=format), t, element, ns_test) assert element[0].text == datetime.date.strftime(t, format) dt = XmlDocument().from_element(None, Date(date_format=format), element[0]) assert t == dt def test_datetime_timezone(self): import pytz n = datetime.datetime.now(pytz.timezone('EST')) element = etree.Element('test') cls = DateTime(as_timezone=pytz.utc, timezone=False) XmlDocument().to_parent(None, cls, n, element, ns_test) element = element[0] c = n.astimezone(pytz.utc).replace(tzinfo=None) self.assertEqual(element.text, c.isoformat()) dt = XmlDocument().from_element(None, cls, element) assert dt.tzinfo is not None dt = dt.replace(tzinfo=None) self.assertEqual(c, dt) def test_date_timezone(self): elt = etree.Element('wot') elt.text = '2013-08-09+02:00' dt = XmlDocument().from_element(None, Date, elt) print("ok without validation.") dt = XmlDocument(validator='soft').from_element(None, Date, elt) print(dt) def test_time(self): n = datetime.time(1, 2, 3, 4) ret = ProtocolBase().to_bytes(Time, n) self.assertEqual(ret, n.isoformat()) dt = ProtocolBase().from_unicode(Time, ret) self.assertEqual(n, dt) def test_time_usec(self): # python's datetime and time only accept ints between [0, 1e6[ # if the incoming data is 999999.8 microseconds, rounding it up means # adding 1 second to time. For many reasons, we want to avoid that. (see # http://bugs.python.org/issue1487389) That's why 999999.8 usec is # rounded to 999999. # rounding 0.1 µsec down t = ProtocolBase().from_unicode(Time, "12:12:12.0000001") self.assertEqual(datetime.time(12, 12, 12), t) # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by # python 2 so we test with 1.5 µsec instead. frikkin' nonsense. t = ProtocolBase().from_unicode(Time, "12:12:12.0000015") self.assertEqual(datetime.time(12, 12, 12, 2), t) # rounding 999998.8 µsec up t = ProtocolBase().from_unicode(Time, "12:12:12.9999988") self.assertEqual(datetime.time(12, 12, 12, 999999), t) # rounding 999999.1 µsec down t = ProtocolBase().from_unicode(Time, "12:12:12.9999991") self.assertEqual(datetime.time(12, 12, 12, 999999), t) # rounding 999999.8 µsec down, not up. t = ProtocolBase().from_unicode(Time, "12:12:12.9999998") self.assertEqual(datetime.time(12, 12, 12, 999999), t) def test_date(self): n = datetime.date(2011, 12, 13) ret = ProtocolBase().to_unicode(Date, n) self.assertEqual(ret, n.isoformat()) dt = ProtocolBase().from_unicode(Date, ret) self.assertEqual(n, dt) def test_utcdatetime(self): datestring = '2007-05-15T13:40:44Z' e = etree.Element('test') e.text = datestring dt = XmlDocument().from_element(None, DateTime, e) self.assertEqual(dt.year, 2007) self.assertEqual(dt.month, 5) self.assertEqual(dt.day, 15) datestring = '2007-05-15T13:40:44.003Z' e = etree.Element('test') e.text = datestring dt = XmlDocument().from_element(None, DateTime, e) self.assertEqual(dt.year, 2007) self.assertEqual(dt.month, 5) self.assertEqual(dt.day, 15) def test_date_exclusive_boundaries(self): test_model = Date.customize(gt=datetime.date(2016, 1, 1), lt=datetime.date(2016, 2, 1)) self.assertFalse( test_model.validate_native(test_model, datetime.date(2016, 1, 1))) self.assertFalse( test_model.validate_native(test_model, datetime.date(2016, 2, 1))) def test_date_inclusive_boundaries(self): test_model = Date.customize(ge=datetime.date(2016, 1, 1), le=datetime.date(2016, 2, 1)) self.assertTrue( test_model.validate_native(test_model, datetime.date(2016, 1, 1))) self.assertTrue( test_model.validate_native(test_model, datetime.date(2016, 2, 1))) def test_datetime_exclusive_boundaries(self): test_model = DateTime.customize( gt=datetime.datetime(2016, 1, 1, 12, 00) .replace(tzinfo=spyne.LOCAL_TZ), lt=datetime.datetime(2016, 2, 1, 12, 00) .replace(tzinfo=spyne.LOCAL_TZ), ) self.assertFalse(test_model.validate_native(test_model, datetime.datetime(2016, 1, 1, 12, 00))) self.assertFalse(test_model.validate_native(test_model, datetime.datetime(2016, 2, 1, 12, 00))) def test_datetime_inclusive_boundaries(self): test_model = DateTime.customize( ge=datetime.datetime(2016, 1, 1, 12, 00) .replace(tzinfo=spyne.LOCAL_TZ), le=datetime.datetime(2016, 2, 1, 12, 00) .replace(tzinfo=spyne.LOCAL_TZ) ) self.assertTrue(test_model.validate_native(test_model, datetime.datetime(2016, 1, 1, 12, 00))) self.assertTrue(test_model.validate_native(test_model, datetime.datetime(2016, 2, 1, 12, 00))) def test_time_exclusive_boundaries(self): test_model = Time.customize(gt=datetime.time(12, 00), lt=datetime.time(13, 00)) self.assertFalse( test_model.validate_native(test_model, datetime.time(12, 00))) self.assertFalse( test_model.validate_native(test_model, datetime.time(13, 00))) def test_time_inclusive_boundaries(self): test_model = Time.customize(ge=datetime.time(12, 00), le=datetime.time(13, 00)) self.assertTrue( test_model.validate_native(test_model, datetime.time(12, 00))) self.assertTrue( test_model.validate_native(test_model, datetime.time(13, 00))) def test_datetime_extreme_boundary(self): self.assertTrue( DateTime.validate_native(DateTime, datetime.datetime.min)) self.assertTrue( DateTime.validate_native(DateTime, datetime.datetime.max)) def test_time_extreme_boundary(self): self.assertTrue(Time.validate_native(Time, datetime.time(0, 0, 0, 0))) self.assertTrue( Time.validate_native(Time, datetime.time(23, 59, 59, 999999))) def test_date_extreme_boundary(self): self.assertTrue(Date.validate_native(Date, datetime.date.min)) self.assertTrue(Date.validate_native(Date, datetime.date.max)) def test_integer(self): i = 12 integer = Integer() element = etree.Element('test') XmlDocument().to_parent(None, Integer, i, element, ns_test) element = element[0] self.assertEqual(element.text, '12') value = XmlDocument().from_element(None, integer, element) self.assertEqual(value, i) def test_integer_limits(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") integer = Integer16(ge=-32768) assert len(w) == 0 integer = Integer16(ge=-32769) assert len(w) == 1 assert issubclass(w[-1].category, NumberLimitsWarning) assert "smaller than min_bound" in str(w[-1].message) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") integer = Integer16(le=32767) assert len(w) == 0 integer = Integer16(le=32768) assert len(w) == 1 assert issubclass(w[-1].category, NumberLimitsWarning) assert "greater than max_bound" in str(w[-1].message) try: Integer16(ge=32768) except ValueError: pass else: raise Exception("must fail") try: Integer16(lt=-32768) except ValueError: pass else: raise Exception("must fail") def test_large_integer(self): i = 128375873458473 integer = Integer() element = etree.Element('test') XmlDocument().to_parent(None, Integer, i, element, ns_test) element = element[0] self.assertEqual(element.text, '128375873458473') value = XmlDocument().from_element(None, integer, element) self.assertEqual(value, i) def test_float(self): f = 1.22255645 element = etree.Element('test') XmlDocument().to_parent(None, Float, f, element, ns_test) element = element[0] self.assertEqual(element.text, repr(f)) f2 = XmlDocument().from_element(None, Float, element) self.assertEqual(f2, f) def test_array(self): type = Array(String) type.resolve_namespace(type, "zbank") values = ['a', 'b', 'c', 'd', 'e', 'f'] element = etree.Element('test') XmlDocument().to_parent(None, type, values, element, ns_test) element = element[0] self.assertEqual(len(values), len(element.getchildren())) values2 = XmlDocument().from_element(None, type, element) self.assertEqual(values[3], values2[3]) def test_array_empty(self): type = Array(String) type.resolve_namespace(type, "zbank") values = [] element = etree.Element('test') XmlDocument().to_parent(None, type, values, element, ns_test) element = element[0] self.assertEqual(len(values), len(element.getchildren())) values2 = XmlDocument().from_element(None, type, element) self.assertEqual(len(values2), 0) def test_unicode(self): s = u'\x34\x55\x65\x34' self.assertEqual(4, len(s)) element = etree.Element('test') XmlDocument().to_parent(None, String, s, element, 'test_ns') element = element[0] value = XmlDocument().from_element(None, String, element) self.assertEqual(value, s) def test_unicode_pattern_mult_cust(self): assert Unicode(pattern='a').Attributes.pattern == 'a' assert Unicode(pattern='a')(5).Attributes.pattern == 'a' def test_unicode_upattern(self): patt = r'[\w .-]+' attr = Unicode(unicode_pattern=patt).Attributes assert attr.pattern == patt assert attr._pattern_re.flags & re.UNICODE assert attr._pattern_re.match(u"Ğ Ğ ç .-") assert attr._pattern_re.match(u"\t") is None def test_unicode_nullable_mult_cust_false(self): assert Unicode(nullable=False).Attributes.nullable == False assert Unicode(nullable=False)(5).Attributes.nullable == False def test_unicode_nullable_mult_cust_true(self): assert Unicode(nullable=True).Attributes.nullable == True assert Unicode(nullable=True)(5).Attributes.nullable == True def test_null(self): element = etree.Element('test') XmlDocument().to_parent(None, Null, None, element, ns_test) print(etree.tostring(element)) element = element[0] self.assertTrue(bool(element.attrib.get(ns.XSI('nil')))) value = XmlDocument().from_element(None, Null, element) self.assertEqual(None, value) def test_point(self): from spyne.model.primitive.spatial import _get_point_pattern a = re.compile(_get_point_pattern(2)) assert a.match('POINT (10 40)') is not None assert a.match('POINT(10 40)') is not None assert a.match('POINT(10.0 40)') is not None assert a.match('POINT(1.310e4 40)') is not None def test_multipoint(self): from spyne.model.primitive.spatial import _get_multipoint_pattern a = re.compile(_get_multipoint_pattern(2)) assert a.match('MULTIPOINT (10 40, 40 30, 20 20, 30 10)') is not None # FIXME: # assert a.match('MULTIPOINT ((10 40), (40 30), (20 20), (30 10))') is not None def test_linestring(self): from spyne.model.primitive.spatial import _get_linestring_pattern a = re.compile(_get_linestring_pattern(2)) assert a.match('LINESTRING (30 10, 10 30, 40 40)') is not None def test_multilinestring(self): from spyne.model.primitive.spatial import _get_multilinestring_pattern a = re.compile(_get_multilinestring_pattern(2)) assert a.match('''MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))''') is not None def test_polygon(self): from spyne.model.primitive.spatial import _get_polygon_pattern a = re.compile(_get_polygon_pattern(2)) assert a.match( 'POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))') is not None def test_multipolygon(self): from spyne.model.primitive.spatial import _get_multipolygon_pattern a = re.compile(_get_multipolygon_pattern(2)) assert a.match('''MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))''') is not None assert a.match('''MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))''') is not None def test_boolean(self): b = etree.Element('test') XmlDocument().to_parent(None, Boolean, True, b, ns_test) b = b[0] self.assertEqual('true', b.text) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, 0, b, ns_test) b = b[0] self.assertEqual('false', b.text) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, 1, b, ns_test) b = b[0] self.assertEqual('true', b.text) b = XmlDocument().from_element(None, Boolean, b) self.assertEqual(b, True) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, False, b, ns_test) b = b[0] self.assertEqual('false', b.text) b = XmlDocument().from_element(None, Boolean, b) self.assertEqual(b, False) b = etree.Element('test') XmlDocument().to_parent(None, Boolean, None, b, ns_test) b = b[0] self.assertEqual('true', b.get(ns.XSI('nil'))) b = XmlDocument().from_element(None, Boolean, b) self.assertEqual(b, None) def test_new_type(self): """Customized primitives go into namespace based on module name.""" custom_type = Unicode(pattern='123') self.assertEqual(custom_type.get_namespace(), custom_type.__module__) def test_default_nullable(self): """Test if default nullable changes nullable attribute.""" try: self.assertTrue(Unicode.Attributes.nullable) orig_default = Unicode.Attributes.NULLABLE_DEFAULT Unicode.Attributes.NULLABLE_DEFAULT = False self.assertFalse(Unicode.Attributes.nullable) self.assertFalse(Unicode.Attributes.nillable) finally: Unicode.Attributes.NULLABLE_DEFAULT = orig_default self.assertEqual(Unicode.Attributes.nullable, orig_default) def test_simple_type_explicit_customization(self): assert Unicode(max_len=5).__extends__ is not None assert Unicode.customize(max_len=5).__extends__ is not None def test_anydict_customization(self): from spyne.model import json assert isinstance( AnyDict.customize(store_as='json').Attributes.store_as, json) def test_uuid_serialize(self): value = uuid.UUID('12345678123456781234567812345678') assert ProtocolBase().to_unicode(Uuid, value) \ == '12345678-1234-5678-1234-567812345678' assert ProtocolBase().to_unicode(Uuid(serialize_as='hex'), value) \ == '12345678123456781234567812345678' assert ProtocolBase().to_unicode(Uuid(serialize_as='urn'), value) \ == 'urn:uuid:12345678-1234-5678-1234-567812345678' assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes'), value) \ == b'\x124Vx\x124Vx\x124Vx\x124Vx' assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes_le'), value) \ == b'xV4\x124\x12xV\x124Vx\x124Vx' assert ProtocolBase().to_unicode(Uuid(serialize_as='fields'), value) \ == (305419896, 4660, 22136, 18, 52, 95073701484152) assert ProtocolBase().to_unicode(Uuid(serialize_as='int'), value) \ == 24197857161011715162171839636988778104 def test_uuid_deserialize(self): value = uuid.UUID('12345678123456781234567812345678') assert ProtocolBase().from_unicode(Uuid, '12345678-1234-5678-1234-567812345678') == value assert ProtocolBase().from_unicode(Uuid(serialize_as='hex'), '12345678123456781234567812345678') == value assert ProtocolBase().from_unicode(Uuid(serialize_as='urn'), 'urn:uuid:12345678-1234-5678-1234-567812345678') == value assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes'), b'\x124Vx\x124Vx\x124Vx\x124Vx') == value assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes_le'), b'xV4\x124\x12xV\x124Vx\x124Vx') == value assert ProtocolBase().from_unicode(Uuid(serialize_as='fields'), (305419896, 4660, 22136, 18, 52, 95073701484152)) == value assert ProtocolBase().from_unicode(Uuid(serialize_as='int'), 24197857161011715162171839636988778104) == value def test_uuid_validate(self): assert Uuid.validate_string(Uuid, '12345678-1234-5678-1234-567812345678') assert Uuid.validate_native(Uuid, uuid.UUID('12345678-1234-5678-1234-567812345678')) def test_datetime_serialize_as(self): i = 1234567890123456 v = datetime.datetime.fromtimestamp(i / 1e6) assert ProtocolBase().to_unicode( DateTime(serialize_as='sec'), v) == i//1e6 assert ProtocolBase().to_unicode( DateTime(serialize_as='sec_float'), v) == i/1e6 assert ProtocolBase().to_unicode( DateTime(serialize_as='msec'), v) == i//1e3 assert ProtocolBase().to_unicode( DateTime(serialize_as='msec_float'), v) == i/1e3 assert ProtocolBase().to_unicode( DateTime(serialize_as='usec'), v) == i def test_datetime_deserialize(self): i = 1234567890123456 v = datetime.datetime.fromtimestamp(i / 1e6) assert ProtocolBase().from_unicode( DateTime(serialize_as='sec'), i//1e6) == \ datetime.datetime.fromtimestamp(i//1e6) assert ProtocolBase().from_unicode( DateTime(serialize_as='sec_float'), i/1e6) == v assert ProtocolBase().from_unicode( DateTime(serialize_as='msec'), i//1e3) == \ datetime.datetime.fromtimestamp(i/1e3//1000) assert ProtocolBase().from_unicode( DateTime(serialize_as='msec_float'), i/1e3) == v assert ProtocolBase().from_unicode( DateTime(serialize_as='usec'), i) == v def test_datetime_ancient(self): t = DateTime(dt_format="%Y-%m-%d %H:%M:%S") # to trigger strftime v = datetime.datetime(1881, 1, 1) vs = '1881-01-01 00:00:00' dt = ProtocolBase().from_unicode(t, vs) self.assertEqual(v, dt) dt = ProtocolBase().to_unicode(t, v) self.assertEqual(vs, dt) def test_custom_strftime(self): s = ProtocolBase.strftime(datetime.date(1800, 9, 23), "%Y has the same days as 1980 and 2008") if s != "1800 has the same days as 1980 and 2008": raise AssertionError(s) print("Testing all day names from 0001/01/01 until 2000/08/01") # Get the weekdays. Can't hard code them; they could be # localized. days = [] for i in range(1, 10): days.append(datetime.date(2000, 1, i).strftime("%A")) nextday = {} for i in range(8): nextday[days[i]] = days[i + 1] startdate = datetime.date(1, 1, 1) enddate = datetime.date(2000, 8, 1) prevday = ProtocolBase.strftime(startdate, "%A") one_day = datetime.timedelta(1) testdate = startdate + one_day while testdate < enddate: if (testdate.day == 1 and testdate.month == 1 and (testdate.year % 100 == 0)): print("Testing century", testdate.year) day = ProtocolBase.strftime(testdate, "%A") if nextday[prevday] != day: raise AssertionError(str(testdate)) prevday = day testdate = testdate + one_day def test_datetime_usec(self): # see the comments on time test for why the rounding here is weird # rounding 0.1 µsec down dt = ProtocolBase().from_unicode(DateTime, "2015-01-01 12:12:12.0000001") self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12), dt) # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by # python 2 so we test with 1.5 µsec instead. frikkin' nonsense. dt = ProtocolBase().from_unicode(DateTime, "2015-01-01 12:12:12.0000015") self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 2), dt) # rounding 999998.8 µsec up dt = ProtocolBase().from_unicode(DateTime, "2015-01-01 12:12:12.9999988") self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) # rounding 999999.1 µsec down dt = ProtocolBase().from_unicode(DateTime, "2015-01-01 12:12:12.9999991") self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) # rounding 999999.8 µsec down, not up. dt = ProtocolBase().from_unicode(DateTime, "2015-01-01 12:12:12.9999998") self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt) ### Duration Data Type ## http://www.w3schools.com/schema/schema_dtypes_date.asp # Duration Data type # The time interval is specified in the following form "PnYnMnDTnHnMnS" where: # P indicates the period (required) # nY indicates the number of years # nM indicates the number of months # nD indicates the number of days # T indicates the start of a time section (*required* if you are going to # specify hours, minutes, seconds or microseconds) # nH indicates the number of hours # nM indicates the number of minutes # nS indicates the number of seconds class SomeBlob(ComplexModel): __namespace__ = 'myns' howlong = Duration() class TestDurationPrimitive(unittest.TestCase): def test_onehour_oneminute_onesecond(self): answer = 'PT1H1M1S' gg = SomeBlob() gg.howlong = timedelta(hours=1, minutes=1, seconds=1) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == gg.howlong.total_seconds() def test_4suite(self): # borrowed from 4Suite tests_seconds = [ (0, u'PT0S'), (1, u'PT1S'), (59, u'PT59S'), (60, u'PT1M'), (3599, u'PT59M59S'), (3600, u'PT1H'), (86399, u'PT23H59M59S'), (86400, u'P1D'), (86400 * 60, u'P60D'), (86400 * 400, u'P400D') ] for secs, answer in tests_seconds: gg = SomeBlob() gg.howlong = timedelta(seconds=secs) element = etree.Element('test') XmlDocument()\ .to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == secs for secs, answer in tests_seconds: if secs > 0: secs *= -1 answer = '-' + answer gg = SomeBlob() gg.howlong = timedelta(seconds=secs) element = etree.Element('test') XmlDocument()\ .to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == secs def test_duration_positive_seconds_only(self): answer = 'PT35S' gg = SomeBlob() gg.howlong = timedelta(seconds=35) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == gg.howlong.total_seconds() def test_duration_positive_minutes_and_seconds_only(self): answer = 'PT5M35S' gg = SomeBlob() gg.howlong = timedelta(minutes=5, seconds=35) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == gg.howlong.total_seconds() def test_duration_positive_milliseconds_only(self): answer = 'PT0.666000S' gg = SomeBlob() gg.howlong = timedelta(milliseconds=666) element = etree.Element('test') XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace()) element = element[0] print(gg.howlong) print(etree.tostring(element, pretty_print=True)) assert element[0].text == answer data = element.find('{%s}howlong' % gg.get_namespace()).text self.assertEqual(data, answer) s1 = XmlDocument().from_element(None, SomeBlob, element) assert s1.howlong.total_seconds() == gg.howlong.total_seconds() def test_duration_xml_duration(self): dur = datetime.timedelta(days=5 + 30 + 365, hours=1, minutes=1, seconds=12, microseconds=8e5) str1 = 'P400DT3672.8S' str2 = 'P1Y1M5DT1H1M12.8S' self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str1)) self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str2)) self.assertEqual(dur, ProtocolBase().from_unicode(Duration, ProtocolBase().to_unicode(Duration, dur))) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/multipython/000077500000000000000000000000001417664205300206115ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/multipython/__init__.py000066400000000000000000000000001417664205300227100ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/multipython/model/000077500000000000000000000000001417664205300217115ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/multipython/model/__init__.py000066400000000000000000000000001417664205300240100ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/multipython/model/test_complex.py000066400000000000000000000140231417664205300247710ustar00rootroot00000000000000# coding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Complex model tests runnable on different Python implementations.""" import unittest from spyne.model.complex import (ComplexModel, ComplexModelMeta, ComplexModelBase, Array) from spyne.model.primitive import Unicode, Integer, String from spyne.util.six import add_metaclass class DeclareOrder_declare(ComplexModel.customize(declare_order='declared')): field3 = Integer field1 = Integer field2 = Integer class MyComplexModelMeta(ComplexModelMeta): """Custom complex model metaclass.""" def __new__(mcs, name, bases, attrs): attrs['new_field'] = Unicode attrs['field1'] = Unicode new_cls = super(MyComplexModelMeta, mcs).__new__(mcs, name, bases, attrs) return new_cls @add_metaclass(MyComplexModelMeta) class MyComplexModel(ComplexModelBase): """Custom complex model class.""" class Attributes(ComplexModelBase.Attributes): declare_order = 'declared' class MyModelWithDeclaredOrder(MyComplexModel): """Test model for complex model with custom metaclass.""" class Attributes(MyComplexModel.Attributes): declare_order = 'declared' field3 = Integer field1 = Integer field2 = Integer class TestComplexModel(unittest.TestCase): def test_add_field(self): class C(ComplexModel): u = Unicode C.append_field('i', Integer) assert C._type_info['i'] is Integer def test_insert_field(self): class C(ComplexModel): u = Unicode C.insert_field(0, 'i', Integer) assert C._type_info.keys() == ['i', 'u'] def test_variants(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(u=dict(min_len=5))) print(dict(C.Attributes._variants.items())) r, = C.Attributes._variants assert r is CC assert CC.Attributes.parent_variant is C C.append_field('i', Integer) assert C._type_info['i'] is Integer assert CC._type_info['i'] is Integer def test_child_customization(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(u=dict(min_len=5))) assert CC._type_info['u'].Attributes.min_len == 5 assert C._type_info['u'].Attributes.min_len != 5 def test_array_customization(self): CC = Array(Unicode).customize( serializer_attrs=dict(min_len=5), punks='roll', ) assert CC.Attributes.punks == 'roll' assert CC._type_info[0].Attributes.min_len == 5 def test_array_customization_complex(self): class C(ComplexModel): u = Unicode CC = Array(C).customize( punks='roll', serializer_attrs=dict(bidik=True) ) assert CC.Attributes.punks == 'roll' assert CC._type_info[0].Attributes.bidik == True def test_delayed_child_customization_append(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(i=dict(ge=5))) CC.append_field('i', Integer) assert CC._type_info['i'].Attributes.ge == 5 assert not 'i' in C._type_info def test_delayed_child_customization_insert(self): class C(ComplexModel): u = Unicode CC = C.customize(child_attrs=dict(i=dict(ge=5))) CC.insert_field(1, 'i', Integer) assert CC._type_info['i'].Attributes.ge == 5 assert not 'i' in C._type_info def test_array_member_name(self): print(Array(String, member_name="punk")._type_info) assert 'punk' in Array(String, member_name="punk")._type_info def test_customize(self): class Base(ComplexModel): class Attributes(ComplexModel.Attributes): prop1 = 3 prop2 = 6 Base2 = Base.customize(prop1=4) self.assertNotEquals(Base.Attributes.prop1, Base2.Attributes.prop1) self.assertEqual(Base.Attributes.prop2, Base2.Attributes.prop2) class Derived(Base): class Attributes(Base.Attributes): prop3 = 9 prop4 = 12 Derived2 = Derived.customize(prop1=5, prop3=12) self.assertEqual(Base.Attributes.prop1, 3) self.assertEqual(Base2.Attributes.prop1, 4) self.assertEqual(Derived.Attributes.prop1, 3) self.assertEqual(Derived2.Attributes.prop1, 5) self.assertNotEquals(Derived.Attributes.prop3, Derived2.Attributes.prop3) self.assertEqual(Derived.Attributes.prop4, Derived2.Attributes.prop4) Derived3 = Derived.customize(prop3=12) Base.prop1 = 4 # changes made to bases propagate, unless overridden self.assertEqual(Derived.Attributes.prop1, Base.Attributes.prop1) self.assertNotEquals(Derived2.Attributes.prop1, Base.Attributes.prop1) self.assertEqual(Derived3.Attributes.prop1, Base.Attributes.prop1) def test_declare_order(self): self.assertEqual(["field3", "field1", "field2"], list(DeclareOrder_declare._type_info)) self.assertEqual(["field3", "field1", "field2", "new_field"], list(MyModelWithDeclaredOrder._type_info)) if __name__ == '__main__': import sys sys.exit(unittest.main()) spyne-spyne-2.14.0/spyne/test/protocol/000077500000000000000000000000001417664205300200565ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/protocol/__init__.py000066400000000000000000000016471417664205300221770ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """This is Spyne's test package. You are not supposed to import test package from production code because tests fiddle with global state of Spyne classes. """ spyne-spyne-2.14.0/spyne/test/protocol/_test_dictdoc.py000066400000000000000000001340721417664205300232460ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import unicode_literals import logging import yaml logger = logging.getLogger(__name__) import unittest import uuid import pytz import decimal from spyne.util import six from spyne.util.dictdoc import get_object_as_dict if not six.PY2: long = int from datetime import datetime from datetime import date from datetime import time from datetime import timedelta import lxml.etree import lxml.html from lxml.builder import E from spyne import MethodContext, Ignored from spyne.service import Service from spyne.server import ServerBase from spyne.application import Application from spyne.decorator import srpc, rpc from spyne.error import ValidationError from spyne.model.binary import binary_encoding_handlers, File from spyne.model.complex import ComplexModel from spyne.model.complex import Iterable from spyne.model.fault import Fault from spyne.protocol import ProtocolBase from spyne.model.binary import ByteArray from spyne.model.primitive import Decimal from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.primitive import DateTime from spyne.model.primitive import Mandatory from spyne.model.primitive import AnyXml from spyne.model.primitive import AnyHtml from spyne.model.primitive import AnyDict from spyne.model.primitive import Unicode from spyne.model.primitive import AnyUri from spyne.model.primitive import ImageUri from spyne.model.primitive import Double from spyne.model.primitive import Integer8 from spyne.model.primitive import Time from spyne.model.primitive import Date from spyne.model.primitive import Duration from spyne.model.primitive import Boolean from spyne.model.primitive import Uuid from spyne.model.primitive import Point from spyne.model.primitive import Line from spyne.model.primitive import Polygon from spyne.model.primitive import MultiPoint from spyne.model.primitive import MultiLine from spyne.model.primitive import MultiPolygon def _unbyte(d): if d is None: return for k, v in list(d.items()): if isinstance(k, bytes): del d[k] d[k.decode('utf8')] = v if isinstance(v, dict): _unbyte(v) for k, v in d.items(): if isinstance(v, (list, tuple)): l = [] for sub in v: if isinstance(sub, dict): l.append(_unbyte(sub)) elif isinstance(sub, bytes): l.append(sub.decode("utf8")) else: l.append(sub) d[k] = tuple(l) elif isinstance(v, bytes): try: d[k] = v.decode('utf8') except UnicodeDecodeError: d[k] = v return d def TDry(serializer, _DictDocumentChild, dumps_kwargs=None): if not dumps_kwargs: dumps_kwargs = {} def _dry_me(services, d, ignore_wrappers=False, complex_as=dict, just_ctx=False, just_in_object=False, validator=None, polymorphic=False): app = Application(services, 'tns', in_protocol=_DictDocumentChild( ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic, validator=validator, ), out_protocol=_DictDocumentChild( ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic), ) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) in_string = serializer.dumps(d, **dumps_kwargs) if not isinstance(in_string, bytes): in_string = in_string.encode('utf8') initial_ctx.in_string = [in_string] ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') if not just_ctx: server.get_in_object(ctx) if not just_in_object: server.get_out_object(ctx) server.get_out_string(ctx) return ctx return _dry_me def TDictDocumentTest(serializer, _DictDocumentChild, dumps_kwargs=None, loads_kwargs=None, convert_dict=None): if not dumps_kwargs: dumps_kwargs = {} if not loads_kwargs: loads_kwargs = {} _dry_me = TDry(serializer, _DictDocumentChild, dumps_kwargs) if convert_dict is None: convert_dict = lambda v: v class Test(unittest.TestCase): def dumps(self, o): print("using", dumps_kwargs, "to dump", o) return serializer.dumps(o, **dumps_kwargs) def loads(self, o): try: return _unbyte(serializer.loads(o, **loads_kwargs)) except TypeError: return _unbyte(serializer.loads(o, Loader=yaml.FullLoader, **loads_kwargs)) def test_complex_with_only_primitive_fields(self): class SomeComplexModel(ComplexModel): i = Integer s = Unicode class SomeService(Service): @srpc(SomeComplexModel, _returns=SomeComplexModel) def some_call(scm): return SomeComplexModel(i=5, s='5x') ctx = _dry_me([SomeService], {"some_call":[]}) s = self.loads(b''.join(ctx.out_string)) s = s["some_callResponse"]["some_callResult"]["SomeComplexModel"] assert s["i"] == 5 assert s["s"] in ("5x", b"5x") def test_complex(self): class CM(ComplexModel): i = Integer s = Unicode class CCM(ComplexModel): c = CM i = Integer s = Unicode class SomeService(Service): @srpc(CCM, _returns=CCM) def some_call(ccm): return CCM(c=ccm.c, i=ccm.i, s=ccm.s) ctx = _dry_me([SomeService], {"some_call": {"ccm": {"CCM":{ "c":{"CM":{"i":3, "s": "3x"}}, "i":4, "s": "4x", }}} }) ret = self.loads(b''.join(ctx.out_string)) print(ret) d = ret['some_callResponse']['some_callResult']['CCM'] assert d['i'] == 4 assert d['s'] in ('4x', b'4x') assert d['c']['CM']['i'] == 3 assert d['c']['CM']['s'] in ('3x', b'3x') def test_multiple_list(self): class SomeService(Service): @srpc(Unicode(max_occurs=decimal.Decimal('inf')), _returns=Unicode(max_occurs=decimal.Decimal('inf'))) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":[["a","b"]]}) data = b''.join(ctx.out_string) print(data) assert self.loads(data) == \ {"some_callResponse": {"some_callResult": ("a", "b")}} def test_multiple_dict(self): class SomeService(Service): @srpc(Unicode(max_occurs=decimal.Decimal('inf')), _returns=Unicode(max_occurs=decimal.Decimal('inf'))) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) assert self.loads(b''.join(ctx.out_string)) == \ {"some_callResponse": {"some_callResult": ("a", "b")}} def test_multiple_dict_array(self): class SomeService(Service): @srpc(Iterable(Unicode), _returns=Iterable(Unicode)) def some_call(s): return s ctx = _dry_me([SomeService], {"some_call":{"s":["a","b"]}}) assert self.loads(b''.join(ctx.out_string)) == \ {"some_callResponse": {"some_callResult": ("a", "b")}} def test_multiple_dict_complex_array(self): class CM(ComplexModel): i = Integer s = Unicode class CCM(ComplexModel): c = CM i = Integer s = Unicode class ECM(CCM): d = DateTime class SomeService(Service): @srpc(Iterable(ECM), _returns=Iterable(ECM)) def some_call(ecm): return ecm ctx = _dry_me([SomeService], { "some_call": {"ecm": [{"ECM": { "c": {"CM":{"i":3, "s": "3x"}}, "i":4, "s": "4x", "d": "2011-12-13T14:15:16Z" }}] }}) print(ctx.in_object) ret = self.loads(b''.join(ctx.out_string)) print(ret) assert ret["some_callResponse"]['some_callResult'] assert ret["some_callResponse"]['some_callResult'][0] assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"] assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["i"] == 3 assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["c"]["CM"]["s"] in ("3x", b"3x") assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["i"] == 4 assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["s"] in ("4x", b"4x") assert ret["some_callResponse"]['some_callResult'][0]["ECM"]["d"] == "2011-12-13T14:15:16+00:00" def test_invalid_request(self): class SomeService(Service): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService], {"some_call": {"yay": []}}, just_in_object=True) print(ctx.in_error) assert ctx.in_error.faultcode == 'Client.ResourceNotFound' def test_invalid_string(self): class SomeService(Service): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i, s, d) ctx = _dry_me([SomeService], {"yay": {"s": 1}}, validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_invalid_number(self): class SomeService(Service): @srpc(Integer, String, DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService], {"yay": ["s", "B"]}, validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_missing_value(self): class SomeService(Service): @srpc(Integer, Unicode, Mandatory.DateTime) def yay(i, s, d): print(i, s, d) ctx = _dry_me([SomeService], {"yay": [1, "B"]}, validator='soft', just_in_object=True) print(ctx.in_error.faultstring) assert ctx.in_error.faultcode == 'Client.ValidationError' assert ctx.in_error.faultstring.endswith("at least 1 times.") def test_invalid_datetime(self): class SomeService(Service): @srpc(Integer, String, Mandatory.DateTime) def yay(i,s,d): print(i,s,d) ctx = _dry_me([SomeService],{"yay": {"d":"a2011"}},validator='soft', just_in_object=True) assert ctx.in_error.faultcode == 'Client.ValidationError' def test_fault_to_dict(self): class SomeService(Service): @srpc(_returns=String) def some_call(): raise Fault() _dry_me([SomeService], {"some_call":[]}) def test_prune_none_and_optional(self): class SomeObject(ComplexModel): i = Integer s = String(min_occurs=1) class SomeService(Service): @srpc(_returns=SomeObject) def some_call(): return SomeObject() ctx = _dry_me([SomeService], {"some_call":[]}) ret = self.loads(b''.join(ctx.out_string)) assert ret == {"some_callResponse": {'some_callResult': {'SomeObject': {'s': None}}}} def test_any_xml(self): d = lxml.etree.tostring(E('{ns1}x', E('{ns2}Y', "some data")), encoding='unicode') class SomeService(Service): @srpc(AnyXml, _returns=AnyXml) def some_call(p): print(p) print(type(p)) assert type(p) == lxml.etree._Element return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_any_html(self): d = lxml.html.tostring(E('div', E('span', "something")), encoding='unicode') class SomeService(Service): @srpc(AnyHtml, _returns=AnyHtml) def some_call(p): print(p) print(type(p)) assert type(p) == lxml.html.HtmlElement return p ctx = _dry_me([SomeService], {"some_call": [d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_any_dict(self): d = {'helo': 213, 'data': {'nested': [12, 0.3]}} class SomeService(Service): @srpc(AnyDict, _returns=AnyDict) def some_call(p): print(p) print(type(p)) assert type(p) == dict return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = b''.join(ctx.out_string) d = self.dumps({"some_callResponse": {"some_callResult": d}}) print(s) print(d) assert self.loads(s) == self.loads(d) def test_unicode(self): d = u'some string' class SomeService(Service): @srpc(Unicode, _returns=Unicode) def some_call(p): print(p) print(type(p)) assert type(p) == six.text_type return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_any_uri(self): d = 'http://example.com/?asd=b12&df=aa#tag' class SomeService(Service): @srpc(AnyUri, _returns=AnyUri) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call": [d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_image_uri(self): d = 'http://example.com/funny.gif' class SomeService(Service): @srpc(ImageUri, _returns=ImageUri) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call": [d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_decimal(self): d = decimal.Decimal('1e100') if _DictDocumentChild._decimal_as_string: d = str(d) class SomeService(Service): @srpc(Decimal, _returns=Decimal) def some_call(p): print(p) print(type(p)) assert type(p) == decimal.Decimal return p ctx = _dry_me([SomeService], {"some_call": [d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_double(self): d = 12.3467 class SomeService(Service): @srpc(Double, _returns=Double) def some_call(p): print(p) print(type(p)) assert type(p) == float return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_integer(self): d = 5 class SomeService(Service): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == int return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_integer_way_small(self): d = -1<<1000 if _DictDocumentChild._huge_numbers_as_string: d = str(d) class SomeService(Service): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == long return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_integer_way_big(self): d = 1<<1000 if _DictDocumentChild._huge_numbers_as_string: d = str(d) class SomeService(Service): @srpc(Integer, _returns=Integer) def some_call(p): print(p) print(type(p)) assert type(p) == long return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_time(self): d = time(10, 20, 30).isoformat() class SomeService(Service): @srpc(Time, _returns=Time) def some_call(p): print(p) print(type(p)) assert type(p) == time assert p.isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_date(self): vdt = datetime(2010, 9, 8) d = vdt.date().isoformat() class SomeService(Service): @srpc(Date, _returns=Date) def some_call(p): print(p) print(type(p)) assert type(p) == date assert p.isoformat() == d return p @srpc(_returns=Date) def some_call_dt(): return vdt ctx = _dry_me([SomeService], {"some_call": [d]}) s = self.loads(b''.join(ctx.out_string)) rd = {"some_callResponse": {"some_callResult": d}} print(s) print(rd) assert s == rd ctx = _dry_me([SomeService], {"some_call_dt": []}) s = self.loads(b''.join(ctx.out_string)) rd = {"some_call_dtResponse": {"some_call_dtResult": d}} print(s) print(rd) assert s == rd def test_datetime(self): d = datetime(2010, 9, 8, 7, 6, 5).isoformat() class SomeService(Service): @srpc(DateTime, _returns=DateTime(timezone=False)) def some_call(p): print(p) print(type(p)) assert type(p) == datetime assert p.replace(tzinfo=None).isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_datetime_tz(self): d = datetime(2010, 9, 8, 7, 6, 5, tzinfo=pytz.utc).isoformat() class SomeService(Service): @srpc(DateTime, _returns=DateTime(ge=datetime(2010,1,1,tzinfo=pytz.utc))) def some_call(p): print(p) print(type(p)) assert type(p) == datetime assert p.isoformat() == d return p ctx = _dry_me([SomeService], {"some_call":[d]}, validator='soft') s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_duration(self): d = ProtocolBase().to_unicode(Duration, timedelta(0, 45)) class SomeService(Service): @srpc(Duration, _returns=Duration) def some_call(p): print(p) print(type(p)) assert type(p) == timedelta return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_boolean(self): d = True class SomeService(Service): @srpc(Boolean, _returns=Boolean) def some_call(p): print(p) print(type(p)) assert type(p) == bool return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_uuid(self): d = '7d2a6330-eb64-4900-8a10-38ebef415e9d' class SomeService(Service): @srpc(Uuid, _returns=Uuid) def some_call(p): print(p) print(type(p)) assert type(p) == uuid.UUID return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_point2d(self): d = 'POINT(1 2)' class SomeService(Service): @srpc(Point, _returns=Point) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_point3d(self): d = 'POINT(1 2 3)' class SomeService(Service): @srpc(Point, _returns=Point) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_line2d(self): d = 'LINESTRING(1 2, 3 4)' class SomeService(Service): @srpc(Line, _returns=Line) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_line3d(self): d = 'LINESTRING(1 2 3, 4 5 6)' class SomeService(Service): @srpc(Line, _returns=Line) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_polygon2d(self): d = 'POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))' class SomeService(Service): @srpc(Polygon(2), _returns=Polygon(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_polygon3d(self): d = 'POLYGON((1 1 0, 1 2 0, 2 2 0, 2 1 0, 1 1 0))' class SomeService(Service): @srpc(Polygon(3), _returns=Polygon(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multipoint2d(self): d = 'MULTIPOINT ((10 40), (40 30), (20 20), (30 10))' class SomeService(Service): @srpc(MultiPoint(2), _returns=MultiPoint(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multipoint3d(self): d = 'MULTIPOINT (10 40 30, 40 30 10,)' class SomeService(Service): @srpc(MultiPoint(3), _returns=MultiPoint(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multiline2d(self): d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' class SomeService(Service): @srpc(MultiLine(2), _returns=MultiLine(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multiline3d(self): d = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' class SomeService(Service): @srpc(MultiLine(3), _returns=MultiLine(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multipolygon2d(self): d = 'MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))' class SomeService(Service): @srpc(MultiPolygon(2), _returns=MultiPolygon(2)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_multipolygon3d(self): d = 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),' \ '((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),' \ '(30 20, 20 25, 20 15, 30 20)))' class SomeService(Service): @srpc(MultiPolygon(3), _returns=MultiPolygon(3)) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return p ctx = _dry_me([SomeService], {"some_call":[d]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": d}} print(s) print(d) assert s == d def test_generator(self): class SomeService(Service): @srpc(_returns=Iterable(Integer)) def some_call(): return iter(range(1000)) ctx = _dry_me([SomeService], {"some_call":[]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": tuple(range(1000))}} print(s) print(d) assert s == d def test_bytearray(self): dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] data = bytes(bytearray(range(0xff))) encoded_data = beh([data]) if _DictDocumentChild.text_based: encoded_data = encoded_data.decode('latin1') class SomeService(Service): @srpc(ByteArray, _returns=ByteArray) def some_call(ba): print(ba) print(type(ba)) assert isinstance(ba, tuple) assert ba == (data,) return ba ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": encoded_data}} print(repr(s)) print(repr(d)) print(repr(encoded_data)) assert s == d def test_file_data(self): # the only difference with the bytearray test is/are the types # inside @srpc dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] data = bytes(bytearray(range(0xff))) encoded_data = beh([data]) if _DictDocumentChild.text_based: encoded_data = encoded_data.decode('latin1') class SomeService(Service): @srpc(File, _returns=File) def some_call(p): print(p) print(type(p)) assert isinstance(p, File.Value) assert p.data == (data,) return p.data # we put the encoded data in the list of arguments. ctx = _dry_me([SomeService], {"some_call": [encoded_data]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": encoded_data}} print(s) print(d) print(repr(encoded_data)) assert s == d def test_file_value(self): dbe = _DictDocumentChild.default_binary_encoding beh = binary_encoding_handlers[dbe] # Prepare data v = File.Value( name='some_file.bin', type='application/octet-stream', ) file_data = bytes(bytearray(range(0xff))) v.data = (file_data,) beh([file_data]) if _DictDocumentChild.text_based: test_data = beh(v.data).decode('latin1') else: test_data = beh(v.data) print(repr(v.data)) class SomeService(Service): @srpc(File, _returns=File) def some_call(p): print(p) print(type(p)) assert isinstance(p, File.Value) assert p.data == (file_data,) assert p.type == v.type assert p.name == v.name return p d = get_object_as_dict(v, File, protocol=_DictDocumentChild, ignore_wrappers=False) ctx = _dry_me([SomeService], {"some_call": {'p': d}}) s = b''.join(ctx.out_string) d = self.dumps({"some_callResponse": {"some_callResult": { 'name': v.name, 'type': v.type, 'data': test_data, }}}) print(self.loads(s)) print(self.loads(d)) print(v) assert self.loads(s) == self.loads(d) def test_ignored(self): class SomeService(Service): @srpc(Unicode, _returns=Unicode) def some_call(p): print(p) print(type(p)) assert isinstance(p, six.string_types) return Ignored("aaa", b=1, c=2) ctx = _dry_me([SomeService], {"some_call": ["some string"]}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {}} assert s == d def test_validation_frequency(self): class SomeService(Service): @srpc(ByteArray(min_occurs=1), _returns=ByteArray) def some_call(p): pass try: _dry_me([SomeService], {"some_call": []}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_nullable(self): class SomeService(Service): @srpc(ByteArray(nullable=False), _returns=ByteArray) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [None]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_string_pattern(self): class SomeService(Service): @srpc(Uuid) def some_call(p): pass try: _dry_me([SomeService], {"some_call": ["duduk"]}, validator='soft') except ValidationError as e: print(e) pass else: raise Exception("must raise ValidationError") def test_validation_integer_range(self): class SomeService(Service): @srpc(Integer(ge=0, le=5)) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [10]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type(self): class SomeService(Service): @srpc(Integer8) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [-129]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type_2(self): class SomeService(Service): @srpc(Integer8) def some_call(p): pass try: _dry_me([SomeService], {"some_call": [1.2]}, validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_not_wrapped(self): class SomeInnerClass(ComplexModel): d = date dt = datetime class SomeClass(ComplexModel): a = int b = Unicode c = SomeInnerClass.customize(not_wrapped=True) class SomeService(Service): @srpc(SomeClass.customize(not_wrapped=True), _returns=SomeClass.customize(not_wrapped=True)) def some_call(p): assert p.a == 1 assert p.b == 's' assert p.c.d == date(2018, 11, 22) return p inner = {"a": 1, "b": "s", "c": {"d": '2018-11-22'}} doc = {"some_call": [inner]} ctx = _dry_me([SomeService], doc, validator='soft') print(ctx.out_document) d = convert_dict({"some_callResponse": {"some_callResult": inner}}) self.assertEquals(ctx.out_document[0], d) def test_validation_freq_parent(self): class C(ComplexModel): i = Integer(min_occurs=1) s = Unicode class SomeService(Service): @srpc(C) def some_call(p): pass try: # must raise validation error for missing i _dry_me([SomeService], {"some_call": {'p': {'C': {'s': 'a'}}}}, validator='soft') except ValidationError as e: logger.exception(e) pass except BaseException as e: logger.exception(e) pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _dry_me([SomeService], {"some_call": {}}, validator='soft') def test_inheritance(self): class P(ComplexModel): identifier = Uuid signature = Unicode class C(P): foo = Unicode bar = Uuid class SomeService(Service): @rpc(_returns=C) def some_call(ctx): result = C() result.identifier = uuid.UUID(int=0) result.signature = 'yyyyyyyyyyy' result.foo = 'zzzzzz' result.bar = uuid.UUID(int=1) return result ctx = _dry_me([SomeService], {"some_call": []}) s = self.loads(b''.join(ctx.out_string)) d = {"some_callResponse": {"some_callResult": {"C": { 'identifier': '00000000-0000-0000-0000-000000000000', 'bar': '00000000-0000-0000-0000-000000000001', 'foo': 'zzzzzz', 'signature': 'yyyyyyyyyyy' }}}} assert s == d def test_exclude(self): class C(ComplexModel): s1 = Unicode(exc=True) s2 = Unicode class SomeService(Service): @srpc(C, _returns=C) def some_call(sc): assert sc.s1 is None, "sc={}".format(sc) assert sc.s2 == "s2" return C(s1="s1", s2="s2") doc = [{"C": {"s1": "s1","s2": "s2"}}] ctx = _dry_me([SomeService], {"some_call": doc}) self.assertEquals(ctx.out_document[0], convert_dict( {'some_callResponse': {'some_callResult': {'C': {'s2': 's2'}}}}) ) def test_polymorphic_deserialization(self): class P(ComplexModel): sig = Unicode class C(P): foo = Unicode class D(P): bar = Integer class SomeService(Service): @rpc(P, _returns=Unicode) def typeof(ctx, p): return type(p).__name__ ctx = _dry_me([SomeService], {"typeof": [{'C':{'sig':'a', 'foo': 'f'}}]}, polymorphic=True) s = self.loads(b''.join(ctx.out_string)) d = {"typeofResponse": {"typeofResult": 'C'}} print(s) print(d) assert s == d ctx = _dry_me([SomeService], {"typeof": [{'D':{'sig':'b', 'bar': 5}}]}, polymorphic=True) s = self.loads(b''.join(ctx.out_string)) d = {"typeofResponse": {"typeofResult": 'D'}} print(s) print(d) assert s == d def test_default(self): class SomeComplexModel(ComplexModel): _type_info = [ ('a', Unicode), ('b', Unicode(default='default')), ] class SomeService(Service): @srpc(SomeComplexModel) def some_method(s): pass ctx = _dry_me([SomeService], {"some_method": [{"s": {"a": "x", "b": None}}]}, polymorphic=True) assert ctx.in_object.s.b == None assert ctx.in_error is None ctx = _dry_me([SomeService], {"some_method": {"s": {"a": "x"}}}, polymorphic=True) assert ctx.in_object.s.b == 'default' assert ctx.in_error is None def test_nillable_default(self): class SomeComplexModel(ComplexModel): _type_info = [ ('a', Unicode), ('b', Unicode(min_occurs=1, default='default', nillable=True)), ] class SomeService(Service): @srpc(SomeComplexModel) def some_method(s): pass ctx = _dry_me([SomeService], {"some_method": [{"s": {"a": "x", "b": None}}]}, polymorphic=True, validator='soft') assert ctx.in_object.s.b == None assert ctx.in_error is None ctx = _dry_me([SomeService], {"some_method": {"s": {"a": "x"}}}, polymorphic=True) assert ctx.in_object.s.b == 'default' assert ctx.in_error is None return Test spyne-spyne-2.14.0/spyne/test/protocol/test_cloth.py000077500000000000000000000373221417664205300226120ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logger = logging.getLogger(__name__) import unittest from lxml import etree, html from lxml.builder import E from spyne import ComplexModel, XmlAttribute, Unicode, Array, Integer, \ SelfReference, XmlData from spyne.protocol.cloth import XmlCloth from spyne.test import FakeContext from spyne.util.six import BytesIO class TestModelCloth(unittest.TestCase): def test_root_html(self): class SomeObject(ComplexModel): class Attributes(ComplexModel.Attributes): html_cloth = html.fromstring("") assert SomeObject.Attributes._html_cloth is None assert SomeObject.Attributes._html_root_cloth is not None def test_html(self): class SomeObject(ComplexModel): class Attributes(ComplexModel.Attributes): html_cloth = html.fromstring('') assert SomeObject.Attributes._html_cloth is not None assert SomeObject.Attributes._html_root_cloth is None def test_root_xml(self): class SomeObject(ComplexModel): class Attributes(ComplexModel.Attributes): xml_cloth = etree.fromstring('') assert SomeObject.Attributes._xml_cloth is None assert SomeObject.Attributes._xml_root_cloth is not None def test_xml(self): class SomeObject(ComplexModel): class Attributes(ComplexModel.Attributes): xml_cloth = html.fromstring('') assert SomeObject.Attributes._xml_cloth is not None assert SomeObject.Attributes._xml_root_cloth is None class TestXmlClothToParent(unittest.TestCase): def setUp(self): self.ctx = FakeContext() self.stream = BytesIO() logging.basicConfig(level=logging.DEBUG) def _run(self, inst, cls=None): if cls is None: cls = inst.__class__ with etree.xmlfile(self.stream) as parent: XmlCloth().subserialize(self.ctx, cls, inst, parent, name=cls.__name__) elt = etree.fromstring(self.stream.getvalue()) print(etree.tostring(elt, pretty_print=True)) return elt def test_simple(self): v = 'punk.' elt = self._run(v, Unicode) assert elt.text == v def test_complex_primitive(self): class SomeObject(ComplexModel): s = Unicode v = 'punk.' elt = self._run(SomeObject(s=v)) assert elt[0].text == v def test_complex_inheritance(self): class A(ComplexModel): i = Integer class B(A): s = Unicode i = 42 s = 'punk.' elt = self._run(B(i=i, s=s)) # order is important assert len(elt) == 2 assert elt[0].text == str(i) assert elt[1].text == s ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! # # This test uses spyne_id and spyne_tagbag instead of spyne-id and spyne-tagbag # for ease of testing. The attributes used here are different from what you are # going to see in the real-world uses of this functionality. # You have been warned !!! # ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! class TestXmlCloth(unittest.TestCase): def setUp(self): self.ctx = FakeContext() self.stream = BytesIO() logging.basicConfig(level=logging.DEBUG) def _run(self, inst, spid=None, cloth=None): cls = inst.__class__ if cloth is None: assert spid is not None cloth = etree.fromstring("""""" % spid) else: assert spid is None with etree.xmlfile(self.stream) as parent: XmlCloth(cloth=cloth).set_identifier_prefix('spyne_') \ .subserialize(self.ctx, cls, inst, parent) elt = etree.fromstring(self.stream.getvalue()) print(etree.tostring(elt, pretty_print=True)) return elt def test_simple_value(self): class SomeObject(ComplexModel): s = Unicode v = 'punk.' elt = self._run(SomeObject(s=v), spid='s') assert elt[0].text == v def test_simple_empty(self): class SomeObject(ComplexModel): s = Unicode elt = self._run(SomeObject(), spid='s') assert len(elt) == 0 # FIXME: just fix it def _test_simple_empty_nonoptional(self): class SomeObject(ComplexModel): s = Unicode(min_occurs=1) elt = self._run(SomeObject(), spid='s') assert elt[0].text is None # FIXME: just fix it def _test_simple_empty_nonoptional_clear(self): class SomeObject(ComplexModel): s = Unicode(min_occurs=1) cloth = etree.fromstring("""oi punk!""") elt = self._run(SomeObject(), cloth=cloth) assert elt[0].text is None def test_xml_data_tag(self): class SomeObject(ComplexModel): d = XmlData(Unicode) cloth = etree.fromstring('') elt = self._run(SomeObject(d='data'), cloth=cloth) assert elt.text == 'data' def test_xml_data_attr(self): class SomeObject(ComplexModel): d = XmlData(Unicode) cloth = etree.fromstring('') elt = self._run(SomeObject(d='data'), cloth=cloth) assert elt.text == 'data' def test_xml_data_attr_undesignated(self): class SomeObject(ComplexModel): d = Unicode cloth = etree.fromstring('') elt = self._run(SomeObject(d='data'), cloth=cloth) assert elt.text == 'data' def test_simple_value_xmlattribute(self): v = 'punk.' class SomeObject(ComplexModel): s = XmlAttribute(Unicode(min_occurs=1)) cloth = etree.fromstring("""""") elt = self._run(SomeObject(s=v), cloth=cloth) assert elt.attrib['s'] == v def test_simple_value_xmlattribute_subname(self): v = 'punk.' class SomeObject(ComplexModel): s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo')) cloth = etree.fromstring("""""") elt = self._run(SomeObject(s=v), cloth=cloth) assert elt.attrib['foo'] == v def test_simple_value_xmlattribute_non_immediate(self): v = 'punk.' class SomeObject(ComplexModel): s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo')) cloth = etree.fromstring("""""") elt = self._run(SomeObject(s=v), cloth=cloth) assert elt.attrib['foo'] == v assert elt[0].attrib['foo'] == v def test_simple_value_xmlattribute_non_immediate_non_designated(self): v = 'punk.' class SomeObject(ComplexModel): s = Unicode(min_occurs=1, sub_name='foo') cloth = etree.fromstring("""""") elt = self._run(SomeObject(s=v), cloth=cloth) assert not 'foo' in elt.attrib assert elt[0].attrib['foo'] == v def test_non_tagbag(self): cloth = E.a( E.b( E.c( E.d( spyne_id="i", ), spyne_id="c", ), spyne_id="i", ), spyne_tagbag='', ) class C2(ComplexModel): i = Integer class C1(ComplexModel): i = Integer c = C2 elt = self._run(C1(i=1, c=C2(i=2)), cloth=cloth) assert elt.xpath('//b/text()') == ['1'] # no order guarantee is given assert set(elt.xpath('//d/text()')) == set(['1', '2']) def test_array(self): v = range(3) class SomeObject(ComplexModel): s = Array(Integer) cloth = E.a( E.b( E.c(spyne_id="integer"), spyne_id="s", ) ) elt = self._run(SomeObject(s=v), cloth=cloth) assert elt.xpath('//c/text()') == [str(i) for i in v] def test_array_empty(self): class SomeObject(ComplexModel): s = Array(Integer) elt_str = '' cloth = etree.fromstring(elt_str) elt = self._run(SomeObject(), cloth=cloth) assert elt.xpath('//c') == [] # FIXME: just fix it def _test_array_empty_nonoptional(self): class SomeObject(ComplexModel): s = Array(Integer(min_occurs=1)) elt_str = '' cloth = etree.fromstring(elt_str) elt = self._run(SomeObject(), cloth=cloth) assert elt.xpath('//c') == [cloth[0][0]] def test_simple_two_tags(self): class SomeObject(ComplexModel): s = Unicode i = Integer v = SomeObject(s='s', i=5) cloth = E.a( E.b1(), E.b2( E.c1(spyne_id="s"), E.c2(), ), E.e( E.g1(), E.g2(spyne_id="i"), E.g3(), ), ) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b1' assert elt[1].tag == 'b2' assert elt[1][0].tag == 'c1' assert elt[1][0].text == 's' assert elt[1][1].tag == 'c2' assert elt[2].tag == 'e' assert elt[2][0].tag == 'g1' assert elt[2][1].tag == 'g2' assert elt[2][1].text == '5' assert elt[2][2].tag == 'g3' def test_sibling_order(self): class SomeObject(ComplexModel): s = Unicode v = SomeObject(s='s') cloth = E.a( E.b1(), E.b2( E.c0(), E.c1(), E.c2(spyne_id="s"), E.c3(), E.c4(), ), ) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b1' assert elt[1].tag == 'b2' assert elt[1][0].tag == 'c0' assert elt[1][1].tag == 'c1' assert elt[1][2].tag == 'c2' assert elt[1][2].text == 's' assert elt[1][3].tag == 'c3' assert elt[1][4].tag == 'c4' def test_parent_text(self): class SomeObject(ComplexModel): s = Unicode v = SomeObject(s='s') cloth = E.a( "text 0", E.b1(spyne_id="s"), ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt.tag == 'a' assert elt.text == 'text 0' assert elt[0].tag == 'b1' assert elt[0].text == 's' def test_anc_text(self): class SomeObject(ComplexModel): s = Unicode v = SomeObject(s='s') cloth = E.a( E.b1( "text 1", E.c1(spyne_id="s"), ) ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b1' assert elt[0].text == 'text 1' assert elt[0][0].tag == 'c1' assert elt[0][0].text == 's' def test_prevsibl_tail(self): class SomeObject(ComplexModel): s = Unicode v = SomeObject(s='s') cloth = E.a( E.b1( E.c1(), "text 2", E.c2(spyne_id="s"), ) ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b1' assert elt[0][0].tag == 'c1' assert elt[0][0].tail == 'text 2' assert elt[0][1].text == 's' def test_sibling_tail_close(self): class SomeObject(ComplexModel): s = Unicode v = SomeObject(s='s') cloth = E.a( E.b0(spyne_id="s"), "text 3", ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b0' assert elt[0].text == 's' assert elt[0].tail == 'text 3' def test_sibling_tail_close_sibling(self): class SomeObject(ComplexModel): s = Unicode i = Integer v = SomeObject(s='s', i=5) cloth = E.a( E.b0(spyne_id="s"), "text 3", E.b1(spyne_id="i"), ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt[0].tag == 'b0' assert elt[0].text == 's' assert elt[0].tail == 'text 3' def test_sibling_tail_close_anc(self): class SomeObject(ComplexModel): s = Unicode i = Integer v = SomeObject(s='s', i=5) cloth = E.a( E.b0(), "text 0", E.b1( E.c0(spyne_id="s"), "text 1", E.c1(), "text 2", ), "text 3", E.b2( E.c1(spyne_id="i"), "text 4", ) ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('/a/b1/c0')[0].tail == 'text 1' assert elt.xpath('/a/b1/c1')[0].tail == 'text 2' assert elt.xpath('/a/b2/c1')[0].tail == 'text 4' def test_nested_conflicts(self): class SomeObject(ComplexModel): s = Unicode i = Integer c = SelfReference v = SomeObject(s='x', i=1, c=SomeObject(s='y', i=2)) cloth = E.a( E.b0(), "text 0", E.b1( E.c0(spyne_id="s"), "text 1", E.c1( E.d0(spyne_id="s"), E.d1(spyne_id="i"), spyne_id="c", ), "text 2", ), "text 3", E.b2( E.c2(spyne_id="i"), "text 4", ) ) print(etree.tostring(cloth, pretty_print=True)) elt = self._run(v, cloth=cloth) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('/a/b1/c0')[0].text == str(v.s) assert elt.xpath('/a/b1/c1/d0')[0].text == str(v.c.s) assert elt.xpath('/a/b1/c1/d1')[0].text == str(v.c.i) assert elt.xpath('/a/b2/c2')[0].text == str(v.i) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_html_microformat.py000077500000000000000000000235401417664205300250440ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import unittest from lxml import html from spyne.application import Application from spyne.decorator import srpc from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.protocol.http import HttpRpc from spyne.protocol.html import HtmlMicroFormat from spyne.service import Service from spyne.server.wsgi import WsgiMethodContext from spyne.server.wsgi import WsgiApplication from spyne.util.test import show, call_wsgi_app_kwargs class TestHtmlMicroFormat(unittest.TestCase): def test_simple(self): class SomeService(Service): @srpc(String, _returns=String) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat(doctype=None)) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 's=s', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error is None server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert b''.join(ctx.out_string) == b'
' \ b'
s
' def test_multiple_return(self): class SomeService(Service): @srpc(_returns=[Integer, String]) def some_call(): return 1, 's' app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat(doctype=None)) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert b''.join(ctx.out_string) == b'
' \ b'
1
' \ b'
s
' def test_complex(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class SomeService(Service): @srpc(CCM, _returns=CCM) def some_call(ccm): return CCM(c=ccm.c,i=ccm.i, s=ccm.s) app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat(doctype=None)) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 'ccm_c_s=abc&ccm_c_i=123&ccm_i=456&ccm_s=def', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) # # Here's what this is supposed to return: # #
#
#
456
#
#
123
#
abc
#
#
def
#
#
# elt = html.fromstring(b''.join(ctx.out_string)) print(html.tostring(elt, pretty_print=True)) resp = elt.find_class('some_callResponse') assert len(resp) == 1 res = resp[0].find_class('some_callResult') assert len(res) == 1 i = res[0].findall('div[@class="i"]') assert len(i) == 1 assert i[0].text == '456' c = res[0].findall('div[@class="c"]') assert len(c) == 1 c_i = c[0].findall('div[@class="i"]') assert len(c_i) == 1 assert c_i[0].text == '123' c_s = c[0].findall('div[@class="s"]') assert len(c_s) == 1 assert c_s[0].text == 'abc' s = res[0].findall('div[@class="s"]') assert len(s) == 1 assert s[0].text == 'def' def test_multiple(self): class SomeService(Service): @srpc(String(max_occurs='unbounded'), _returns=String) def some_call(s): print(s) return '\n'.join(s) app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat(doctype=None)) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': 's=1&s=2', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert b''.join(ctx.out_string) == (b'
' b'
1\n2
') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) assert b''.join(ctx.out_string) == b'
' \ b'
1\n2
' def test_before_first_root(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class SomeService(Service): @srpc(CCM, _returns=Array(CCM)) def some_call(ccm): return [CCM(c=ccm.c,i=ccm.i, s=ccm.s)] * 2 cb_called = [False] def _cb(ctx, cls, inst, parent, name, **kwargs): assert not cb_called[0] cb_called[0] = True app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat( doctype=None, before_first_root=_cb)) server = WsgiApplication(app) call_wsgi_app_kwargs(server, ccm_c_s='abc', ccm_c_i=123, ccm_i=456, ccm_s='def') assert cb_called[0] def test_complex_array(self): class CM(ComplexModel): i = Integer s = String class CCM(ComplexModel): c = CM i = Integer s = String class SomeService(Service): @srpc(CCM, _returns=Array(CCM)) def some_call(ccm): return [CCM(c=ccm.c,i=ccm.i, s=ccm.s)] * 2 app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim='_'), out_protocol=HtmlMicroFormat(doctype=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, ccm_c_s='abc', ccm_c_i=123, ccm_i=456, ccm_s='def') # # Here's what this is supposed to return: # #
#
#
456
#
#
123
#
abc
#
#
def
#
#
#
456
#
#
123
#
abc
#
#
def
#
#
# print(out_string) elt = html.fromstring(out_string) show(elt, "TestHtmlMicroFormat.test_complex_array") resp = elt.find_class('some_callResponse') assert len(resp) == 1 res = resp[0].find_class('some_callResult') assert len(res) == 1 assert len(res[0].find_class("CCM")) == 2 # We don't need to test the rest as the test_complex test takes care of # that if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_html_table.py000077500000000000000000000423011417664205300236050ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import unittest from lxml import etree, html from spyne.application import Application from spyne.decorator import srpc from spyne.model.primitive import Integer, Unicode from spyne.model.primitive import String from spyne.model.primitive import AnyUri from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.protocol.http import HttpRpc from spyne.protocol.html.table import HtmlColumnTable, HtmlRowTable from spyne.service import Service from spyne.server.wsgi import WsgiApplication from spyne.util.test import show, call_wsgi_app_kwargs, call_wsgi_app class CM(ComplexModel): _type_info = [ ('i', Integer), ('s', String), ] class CCM(ComplexModel): _type_info = [ ('c', CM), ('i', Integer), ('s', String), ] class TestHtmlColumnTable(unittest.TestCase): def test_complex_array(self): class SomeService(Service): @srpc(CCM, _returns=Array(CCM)) def some_call(ccm): return [ccm] * 5 app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, ccm_i='456', ccm_s='def', ccm_c_i='123', ccm_c_s='abc', ) elt = etree.fromstring(out_string) show(elt, 'TestHtmlColumnTable.test_complex_array') elt = html.fromstring(out_string) row, = elt[0] # thead cell = row.findall('th[@class="i"]') assert len(cell) == 1 assert cell[0].text == 'i' cell = row.findall('th[@class="s"]') assert len(cell) == 1 assert cell[0].text == 's' for row in elt[1]: # tbody cell = row.xpath('td[@class="i"]') assert len(cell) == 1 assert cell[0].text == '456' cell = row.xpath('td[@class="c"]//td[@class="i"]') assert len(cell) == 1 assert cell[0].text == '123' cell = row.xpath('td[@class="c"]//td[@class="s"]') assert len(cell) == 1 assert cell[0].text == 'abc' cell = row.xpath('td[@class="s"]') assert len(cell) == 1 assert cell[0].text == 'def' def test_string_array(self): class SomeService(Service): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable( field_name_attr=None, field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2'))) elt = etree.fromstring(out_string) show(elt, "TestHtmlColumnTable.test_string_array") assert out_string.decode('utf8') == \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
some_callResponse
1
2
' def test_anyuri_string(self): _link = "http://arskom.com.tr/" class C(ComplexModel): c = AnyUri class SomeService(Service): @srpc(_returns=Array(C)) def some_call(): return [C(c=_link)] app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) show(elt, "TestHtmlColumnTable.test_anyuri_string") assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_anyuri_uri_value(self): _link = "http://arskom.com.tr/" _text = "Arskom" class C(ComplexModel): c = AnyUri class SomeService(Service): @srpc(_returns=Array(C)) def some_call(): return [C(c=AnyUri.Value(_link, text=_text))] app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlColumnTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].text == _text assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_row_subprot(self): from lxml.html.builder import E from spyne.protocol.html import HtmlBase from spyne.util.six.moves.urllib.parse import urlencode from spyne.protocol.html import HtmlMicroFormat class SearchProtocol(HtmlBase): def to_parent(self, ctx, cls, inst, parent, name, **kwargs): s = self.to_unicode(cls._type_info['query'], inst.query) q = urlencode({"q": s}) parent.write(E.a("Search %s" % inst.query, href="{}?{}".format(inst.uri, q))) def column_table_gen_header(self, ctx, cls, parent, name): parent.write(E.thead(E.th("Search", **{'class': 'search-link'}))) def column_table_before_row(self, ctx, cls, inst, parent, name,**_): ctxstack = getattr(ctx.protocol[self], 'array_subprot_ctxstack', []) tr_ctx = parent.element('tr') tr_ctx.__enter__() ctxstack.append(tr_ctx) td_ctx = parent.element('td', **{'class': "search-link"}) td_ctx.__enter__() ctxstack.append(td_ctx) ctx.protocol[self].array_subprot_ctxstack = ctxstack def column_table_after_row(self, ctx, cls, inst, parent, name, **kwargs): ctxstack = ctx.protocol[self].array_subprot_ctxstack for elt_ctx in reversed(ctxstack): elt_ctx.__exit__(None, None, None) del ctxstack[:] class Search(ComplexModel): query = Unicode uri = Unicode SearchTable = Array( Search.customize(prot=SearchProtocol()), prot=HtmlColumnTable(field_type_name_attr=None), ) class SomeService(Service): @srpc(_returns=SearchTable) def some_call(): return [ Search(query='Arskom', uri='https://www.google.com/search'), Search(query='Spyne', uri='https://www.bing.com/search'), ] app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlMicroFormat()) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="search-link"]/a/text()') == \ ['Search Arskom', 'Search Spyne'] assert elt.xpath('//td[@class="search-link"]/a/@href') == [ 'https://www.google.com/search?q=Arskom', 'https://www.bing.com/search?q=Spyne', ] assert elt.xpath('//th[@class="search-link"]/text()') == ["Search"] class TestHtmlRowTable(unittest.TestCase): def test_anyuri_string(self): _link = "http://arskom.com.tr/" class C(ComplexModel): c = AnyUri class SomeService(Service): @srpc(_returns=C) def some_call(): return C(c=_link) app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_anyuri_uri_value(self): _link = "http://arskom.com.tr/" _text = "Arskom" class C(ComplexModel): c = AnyUri class SomeService(Service): @srpc(_returns=C) def some_call(): return C(c=AnyUri.Value(_link, text=_text)) app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) elt = html.fromstring(out_string) print(html.tostring(elt, pretty_print=True)) assert elt.xpath('//td[@class="c"]')[0][0].tag == 'a' assert elt.xpath('//td[@class="c"]')[0][0].text == _text assert elt.xpath('//td[@class="c"]')[0][0].attrib['href'] == _link def test_complex(self): class SomeService(Service): @srpc(CCM, _returns=CCM) def some_call(ccm): return ccm app = Application([SomeService], 'tns', in_protocol=HttpRpc(hier_delim="_"), out_protocol=HtmlRowTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server, 'some_call', ccm_c_s='abc', ccm_c_i='123', ccm_i='456', ccm_s='def') elt = html.fromstring(out_string) show(elt, "TestHtmlRowTable.test_complex") # Here's what this is supposed to return """
i 456
c
i 123
s abc
s def
""" print(html.tostring(elt, pretty_print=True)) resp = elt.find_class('CCM') assert len(resp) == 1 assert elt.xpath('tbody/tr/th[@class="i"]/text()')[0] == 'i' assert elt.xpath('tbody/tr/td[@class="i"]/text()')[0] == '456' assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="i"]/text()')[0] == 'i' assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="i"]/text()')[0] == '123' assert elt.xpath('tbody/tr/td[@class="c"]//th[@class="s"]/text()')[0] == 's' assert elt.xpath('tbody/tr/td[@class="c"]//td[@class="s"]/text()')[0] == 'abc' assert elt.xpath('tbody/tr/th[@class="s"]/text()')[0] == 's' assert elt.xpath('tbody/tr/td[@class="s"]/text()')[0] == 'def' def test_string_array(self): class SomeService(Service): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_name_attr=None, field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array') assert out_string.decode('utf8') == \ '
' \ '' \ '' \ '' \ '' \ '' \ '
string' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
1
2
' \ '
' \ '
' def test_string_array_no_header(self): class SomeService(Service): @srpc(String(max_occurs='unbounded'), _returns=Array(String)) def some_call(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(header=False, field_name_attr=None, field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app(server, body_pairs=(('s', '1'), ('s', '2')) ) #FIXME: Needs a proper test with xpaths and all. show(html.fromstring(out_string), 'TestHtmlRowTable.test_string_array_no_header') assert out_string.decode('utf8') == \ '
' \ '' \ '' \ '' \ '' \ '
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
1
2
' \ '
' \ '
' def test_complex_array(self): v = [ CM(i=1, s='a'), CM(i=2, s='b'), CM(i=3, s='c'), CM(i=4, s='d'), ] class SomeService(Service): @srpc(_returns=Array(CM)) def some_call(): return v app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HtmlRowTable(field_type_name_attr=None)) server = WsgiApplication(app) out_string = call_wsgi_app_kwargs(server) show(html.fromstring(out_string), 'TestHtmlRowTable.test_complex_array') #FIXME: Needs a proper test with xpaths and all. assert out_string.decode('utf8') == \ '
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i1
sa
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i2
sb
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i3
sc
' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '' \ '
i4
sd
' \ '
' if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_http.py000077500000000000000000000674461417664205300224720ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import unittest from spyne.util.six import StringIO from spyne.util.six.moves.http_cookies import SimpleCookie from datetime import datetime from wsgiref.validate import validator as wsgiref_validator from spyne.server.wsgi import _parse_qs from spyne.application import Application from spyne.error import ValidationError from spyne.const.http import HTTP_200 from spyne.decorator import rpc from spyne.decorator import srpc from spyne.model import ByteArray, DateTime, Uuid, String, Integer, Integer8, \ ComplexModel, Array from spyne.protocol.http import HttpRpc, HttpPattern, _parse_cookie from spyne.service import Service from spyne.server.wsgi import WsgiApplication, WsgiMethodContext from spyne.server.http import HttpTransportContext from spyne.util.test import call_wsgi_app_kwargs class TestString(unittest.TestCase): def setUp(self): class SomeService(Service): @srpc(String, _returns=String) def echo_string(s): return s app = Application([SomeService], 'tns', in_protocol=HttpRpc(validator='soft'), out_protocol=HttpRpc(), ) self.app = WsgiApplication(app) def test_without_content_type(self): headers = None ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == b'string' def test_without_encoding(self): headers = {'CONTENT_TYPE':'text/plain'} ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == b'string' def test_with_encoding(self): headers = {'CONTENT_TYPE':'text/plain; charset=utf8'} ret = call_wsgi_app_kwargs(self.app, 'echo_string', headers, s="string") assert ret == b'string' class TestHttpTransportContext(unittest.TestCase): def test_gen_header(self): val = HttpTransportContext.gen_header("text/plain", charset="utf8") assert val == 'text/plain; charset="utf8"' class TestSimpleDictDocument(unittest.TestCase): def test_own_parse_qs_01(self): assert dict(_parse_qs('')) == {} def test_own_parse_qs_02(self): assert dict(_parse_qs('p')) == {'p': [None]} def test_own_parse_qs_03(self): assert dict(_parse_qs('p=')) == {'p': ['']} def test_own_parse_qs_04(self): assert dict(_parse_qs('p=1')) == {'p': ['1']} def test_own_parse_qs_05(self): assert dict(_parse_qs('p=1&')) == {'p': ['1']} def test_own_parse_qs_06(self): assert dict(_parse_qs('p=1&q')) == {'p': ['1'], 'q': [None]} def test_own_parse_qs_07(self): assert dict(_parse_qs('p=1&q=')) == {'p': ['1'], 'q': ['']} def test_own_parse_qs_08(self): assert dict(_parse_qs('p=1&q=2')) == {'p': ['1'], 'q': ['2']} def test_own_parse_qs_09(self): assert dict(_parse_qs('p=1&q=2&p')) == {'p': ['1', None], 'q': ['2']} def test_own_parse_qs_10(self): assert dict(_parse_qs('p=1&q=2&p=')) == {'p': ['1', ''], 'q': ['2']} def test_own_parse_qs_11(self): assert dict(_parse_qs('p=1&q=2&p=3')) == {'p': ['1', '3'], 'q': ['2']} def _test(services, qs, validator='soft', strict_arrays=False): app = Application(services, 'tns', in_protocol=HttpRpc(validator=validator, strict_arrays=strict_arrays), out_protocol=HttpRpc()) server = WsgiApplication(app) initial_ctx = WsgiMethodContext(server, { 'QUERY_STRING': qs, 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': "localhost", }, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) if ctx.in_error is not None: raise ctx.in_error server.get_out_object(ctx) if ctx.out_error is not None: raise ctx.out_error server.get_out_string(ctx) return ctx class TestValidation(unittest.TestCase): def test_validation_frequency(self): class SomeService(Service): @srpc(ByteArray(min_occurs=1), _returns=ByteArray) def some_call(p): pass try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def _test_validation_frequency_simple_bare(self): class SomeService(Service): @srpc(ByteArray(min_occurs=1), _body_style='bare', _returns=ByteArray) def some_call(p): pass try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_frequency_complex_bare_parent(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(Service): @srpc(C, _body_style='bare') def some_call(p): pass # must not complain about missing s _test([SomeService], 'i=5', validator='soft') # must raise validation error for missing i try: _test([SomeService], 's=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must raise validation error for missing i try: _test([SomeService], '', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_frequency_parent(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(Service): @srpc(C) def some_call(p): pass # must not complain about missing s _test([SomeService], 'p.i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p.s=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_array(self): class C(ComplexModel): i=Integer(min_occurs=1) s=String class SomeService(Service): @srpc(Array(C)) def some_call(p): pass # must not complain about missing s _test([SomeService], 'p[0].i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p[0].s=a', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_array_index_jump_error(self): class C(ComplexModel): i=Integer class SomeService(Service): @srpc(Array(C), _returns=String) def some_call(p): return repr(p) try: # must raise validation error for index jump from 0 to 2 even without # any validation _test([SomeService], 'p[0].i=42&p[2].i=42&', strict_arrays=True) except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_array_index_jump_tolerate(self): class C(ComplexModel): i=Integer class SomeService(Service): @srpc(Array(C), _returns=String) def some_call(p): return repr(p) # must not raise validation error for index jump from 0 to 2 and ignore # element with index 1 ret = _test([SomeService], 'p[0].i=0&p[2].i=2&', strict_arrays=False) assert ret.out_object[0] == '[C(i=0), C(i=2)]' # even if they arrive out-of-order. ret = _test([SomeService], 'p[2].i=2&p[0].i=0&', strict_arrays=False) assert ret.out_object[0] == '[C(i=0), C(i=2)]' def test_validation_nested_array(self): class CC(ComplexModel): d = DateTime class C(ComplexModel): i = Integer(min_occurs=1) cc = Array(CC) class SomeService(Service): @srpc(Array(C)) def some_call(p): print(p) # must not complain about missing s _test([SomeService], 'p[0].i=5', validator='soft') try: # must raise validation error for missing i _test([SomeService], 'p[0].cc[0].d=2013-01-01', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") # must not raise anything for missing p because C has min_occurs=0 _test([SomeService], '', validator='soft') def test_validation_nullable(self): class SomeService(Service): @srpc(ByteArray(nullable=False), _returns=ByteArray) def some_call(p): pass try: _test([SomeService], 'p', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_string_pattern(self): class SomeService(Service): @srpc(Uuid) def some_call(p): pass try: _test([SomeService], "p=duduk", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_range(self): class SomeService(Service): @srpc(Integer(ge=0, le=5)) def some_call(p): pass try: _test([SomeService], 'p=10', validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type(self): class SomeService(Service): @srpc(Integer8) def some_call(p): pass try: _test([SomeService], "p=-129", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") def test_validation_integer_type_2(self): class SomeService(Service): @srpc(Integer8) def some_call(p): pass try: _test([SomeService], "p=1.2", validator='soft') except ValidationError: pass else: raise Exception("must raise ValidationError") class Test(unittest.TestCase): def test_multiple_return(self): class SomeService(Service): @srpc(_returns=[Integer, String]) def some_call(): return 1, 's' try: _test([SomeService], '') except TypeError: pass else: raise Exception("Must fail with: HttpRpc does not support complex " "return types.") def test_primitive_only(self): class SomeComplexModel(ComplexModel): i = Integer s = String class SomeService(Service): @srpc(SomeComplexModel, _returns=SomeComplexModel) def some_call(scm): return SomeComplexModel(i=5, s='5x') try: _test([SomeService], '') except TypeError: pass else: raise Exception("Must fail with: HttpRpc does not support complex " "return types.") def test_complex(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(Service): @srpc(CCM, _returns=String) def some_call(ccm): return repr(CCM(c=ccm.c, i=ccm.i, s=ccm.s)) ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') assert ctx.out_string[0] == b"CCM(i=1, c=CM(i=3, s='cs'), s='s')" def test_simple_array(self): class SomeService(Service): @srpc(String(max_occurs='unbounded'), _returns=String) def some_call(s): return '\n'.join(s) ctx = _test([SomeService], '&s=1&s=2') assert b''.join(ctx.out_string) == b'1\n2' def test_complex_array(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class SomeService(Service): @srpc(Array(CM), _returns=String) def some_call(cs): return '\n'.join([repr(c) for c in cs]) ctx = _test([SomeService], 'cs[0].i=1&cs[0].s=x' '&cs[1].i=2&cs[1].s=y' '&cs[2].i=3&cs[2].s=z') assert b''.join(ctx.out_string) == \ b"CM(i=1, s='x')\n" \ b"CM(i=2, s='y')\n" \ b"CM(i=3, s='z')" def test_complex_array_empty(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class SomeService(Service): @srpc(Array(CM), _returns=String) def some_call(cs): return repr(cs) ctx = _test([SomeService], 'cs=empty') assert b''.join(ctx.out_string) == b'[]' def test_complex_object_empty(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class SomeService(Service): @srpc(CM, _returns=String) def some_call(c): return repr(c) ctx = _test([SomeService], 'c=empty') assert b''.join(ctx.out_string) == b'CM()' def test_nested_flatten(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(Service): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], '&ccm.i=1&ccm.s=s&ccm.c.i=3&ccm.c.s=cs') print(ctx.out_string) assert b''.join(ctx.out_string) == b"CCM(i=1, c=CM(i=3, s='cs'), s='s')" def test_nested_flatten_with_multiple_values_1(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM), ("s", String), ] class SomeService(Service): @srpc(CCM.customize(max_occurs=2), _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' '&ccm[0].c.i=1&ccm[0].c.s=a' '&ccm[1].c.i=2&ccm[1].c.s=b') s = b''.join(ctx.out_string) assert s == b"[CCM(i=1, c=CM(i=1, s='a'), s='s'), CCM(c=CM(i=2, s='b'))]" def test_nested_flatten_with_multiple_values_2(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", CM.customize(max_occurs=2)), ("s", String), ] class SomeService(Service): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c[0].i=1&ccm.c[0].s=a' '&ccm.c[1].i=2&ccm.c[1].s=b') s = b''.join(list(ctx.out_string)) assert s == b"CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" def test_nested_flatten_with_complex_array(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String), ] class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(CM)), ("s", String), ] class SomeService(Service): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c[0].i=1&ccm.c[0].s=a' '&ccm.c[1].i=2&ccm.c[1].s=b') s = b''.join(list(ctx.out_string)) assert s == b"CCM(i=1, c=[CM(i=1, s='a'), CM(i=2, s='b')], s='s')" def test_nested_2_flatten_with_primitive_array(self): class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(String)), ("s", String), ] class SomeService(Service): @srpc(Array(CCM), _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm[0].i=1&ccm[0].s=s' '&ccm[0].c=a' '&ccm[0].c=b') s = b''.join(list(ctx.out_string)) assert s == b"[CCM(i=1, c=['a', 'b'], s='s')]" def test_default(self): class CM(ComplexModel): _type_info = [ ("i", Integer), ("s", String(default='default')), ] class SomeService(Service): @srpc(CM, _returns=String) def some_call(cm): return repr(cm) # s is missing ctx = _test([SomeService], 'cm.i=1') s = b''.join(ctx.out_string) assert s == b"CM(i=1, s='default')" # s is None ctx = _test([SomeService], 'cm.i=1&cm.s') s = b''.join(ctx.out_string) assert s == b"CM(i=1)" # s is empty ctx = _test([SomeService], 'cm.i=1&cm.s=') s = b''.join(ctx.out_string) assert s == b"CM(i=1, s='')" def test_nested_flatten_with_primitive_array(self): class CCM(ComplexModel): _type_info = [ ("i", Integer), ("c", Array(String)), ("s", String), ] class SomeService(Service): @srpc(CCM, _returns=String) def some_call(ccm): return repr(ccm) ctx = _test([SomeService], 'ccm.i=1&ccm.s=s' '&ccm.c=a' '&ccm.c=b') s = b''.join(list(ctx.out_string)) assert s == b"CCM(i=1, c=['a', 'b'], s='s')" ctx = _test([SomeService], 'ccm.i=1' '&ccm.s=s' '&ccm.c[1]=b' '&ccm.c[0]=a') s = b''.join(list(ctx.out_string)) assert s == b"CCM(i=1, c=['a', 'b'], s='s')" ctx = _test([SomeService], 'ccm.i=1' '&ccm.s=s' '&ccm.c[0]=a' '&ccm.c[1]=b') s = b''.join(list(ctx.out_string)) assert s == b"CCM(i=1, c=['a', 'b'], s='s')" def test_http_headers(self): d = datetime(year=2013, month=1, day=1) string = ['hey', 'yo'] class ResponseHeader(ComplexModel): _type_info = { 'Set-Cookie': String(max_occurs='unbounded'), 'Expires': DateTime } class SomeService(Service): __out_header__ = ResponseHeader @rpc(String) def some_call(ctx, s): assert s is not None ctx.out_header = ResponseHeader(**{'Set-Cookie': string, 'Expires': d}) def start_response(code, headers): print(headers) assert len([s for s in string if ('Set-Cookie', s) in headers]) == len(string) assert dict(headers)['Expires'] == 'Tue, 01 Jan 2013 00:00:00 GMT' app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) wsgi_app = WsgiApplication(app) req_dict = { 'SCRIPT_NAME': '', 'QUERY_STRING': '&s=foo', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'SERVER_PORT': "9999", 'wsgi.url_scheme': 'http', 'wsgi.version': (1,0), 'wsgi.input': StringIO(), 'wsgi.errors': StringIO(), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': True, } ret = wsgi_app(req_dict, start_response) print(list(ret)) wsgi_app = wsgiref_validator(wsgi_app) ret = wsgi_app(req_dict, start_response) assert list(ret) == [b''] class TestHttpPatterns(unittest.TestCase): def test_rules(self): _int = 5 _fragment = 'some_fragment' class SomeService(Service): @srpc(Integer, _returns=Integer, _patterns=[ HttpPattern('/%s/' % _fragment)]) def some_call(some_int): assert some_int == _int app = Application([SomeService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) environ = { 'QUERY_STRING': '', 'PATH_INFO': '/%s/%d' % (_fragment, _int), 'SERVER_PATH':"/", 'SERVER_NAME': "localhost", 'wsgi.url_scheme': 'http', 'SERVER_PORT': '9000', 'REQUEST_METHOD': 'GET', } initial_ctx = WsgiMethodContext(server, environ, 'some-content-type') ctx, = server.generate_contexts(initial_ctx) foo = [] for i in server._http_patterns: foo.append(i) assert len(foo) == 1 print(foo) assert ctx.descriptor is not None server.get_in_object(ctx) assert ctx.in_error is None server.get_out_object(ctx) assert ctx.out_error is None class ParseCookieTest(unittest.TestCase): def test_cookie_parse(self): string = 'some_string' class RequestHeader(ComplexModel): some_field = String class SomeService(Service): __in_header__ = RequestHeader @rpc(String) def some_call(ctx, s): assert ctx.in_header.some_field == string def start_response(code, headers): assert code == HTTP_200 c = 'some_field=%s'% (string,) app = Application([SomeService], 'tns', in_protocol=HttpRpc(parse_cookie=True), out_protocol=HttpRpc()) wsgi_app = WsgiApplication(app) req_dict = { 'SCRIPT_NAME': '', 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'localhost', 'SERVER_PORT': "9999", 'HTTP_COOKIE': c, 'wsgi.url_scheme': 'http', 'wsgi.version': (1,0), 'wsgi.input': StringIO(), 'wsgi.errors': StringIO(), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': True, } ret = wsgi_app(req_dict, start_response) print(ret) wsgi_app = wsgiref_validator(wsgi_app) ret = wsgi_app(req_dict, start_response) print(ret) # These tests copied from Django: # https://github.com/django/django/pull/6277/commits/da810901ada1cae9fc1f018f879f11a7fb467b28 def test_python_cookies(self): """ Test cases copied from Python's Lib/test/test_http_cookies.py """ self.assertEqual(_parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'}) # Here _parse_cookie() differs from Python's cookie parsing in that it # treats all semicolons as delimiters, even within quotes. self.assertEqual( _parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'), {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'} ) # Illegal cookies that have an '=' char in an unquoted value. self.assertEqual(_parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'}) # Cookies with ':' character in their name. self.assertEqual(_parse_cookie('key:term=value:term'), {'key:term': 'value:term'}) # Cookies with '[' and ']'. self.assertEqual(_parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'}) def test_cookie_edgecases(self): # Cookies that RFC6265 allows. self.assertEqual(_parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'}) # _parse_cookie() has historically kept only the last cookie with the # same name. self.assertEqual(_parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'}) def test_invalid_cookies(self): """ Cookie strings that go against RFC6265 but browsers will send if set via document.cookie. """ # Chunks without an equals sign appear as unnamed values per # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 self.assertIn('django_language', _parse_cookie('abc=def; unnamed; django_language=en').keys()) # Even a double quote may be an unamed value. self.assertEqual( _parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'}) # Spaces in names and values, and an equals sign in values. self.assertEqual(_parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'}) # More characters the spec forbids. self.assertEqual(_parse_cookie('a b,c<>@:/[]?{}=d " =e,f g'), {'a b,c<>@:/[]?{}': 'd " =e,f g'}) # Unicode characters. The spec only allows ASCII. self.assertEqual(_parse_cookie(u'saint=André Bessette'), {u'saint': u'André Bessette'}) # Browsers don't send extra whitespace or semicolons in Cookie headers, # but _parse_cookie() should parse whitespace the same way # document.cookie parses whitespace. self.assertEqual(_parse_cookie(' = b ; ; = ; c = ; '), {'': 'b', 'c': ''}) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_json.py000077500000000000000000000156521417664205300224540ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest try: import simplejson as json except ImportError: import json from spyne import MethodContext from spyne import Application from spyne import rpc,srpc from spyne import Service from spyne.model import Integer, Unicode, ComplexModel from spyne.protocol.json import JsonP from spyne.protocol.json import JsonDocument from spyne.protocol.json import JsonEncoder from spyne.protocol.json import _SpyneJsonRpc1 from spyne.server import ServerBase from spyne.server.null import NullServer from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.test.protocol._test_dictdoc import TDry class TestDictDocument(TDictDocumentTest(json, JsonDocument, dumps_kwargs=dict(cls=JsonEncoder))): def dumps(self, o): return super(TestDictDocument, self).dumps(o).encode('utf8') def loads(self, o): return super(TestDictDocument, self).loads(o.decode('utf8')) _dry_sjrpc1 = TDry(json, _SpyneJsonRpc1) class TestSpyneJsonRpc1(unittest.TestCase): def test_call(self): class SomeService(Service): @srpc(Integer, _returns=Integer) def yay(i): print(i) return i ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"yay": {"i":5}}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "body": 5} def test_call_with_header(self): class SomeHeader(ComplexModel): i = Integer class SomeService(Service): __in_header__ = SomeHeader @rpc(Integer, _returns=Integer) def yay(ctx, i): print(ctx.in_header) return ctx.in_header.i ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"yay": None}, "head": {"i":5}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "body": 5} def test_error(self): class SomeHeader(ComplexModel): i = Integer class SomeService(Service): __in_header__ = SomeHeader @rpc(Integer, Integer, _returns=Integer) def div(ctx, dividend, divisor): return dividend / divisor ctx = _dry_sjrpc1([SomeService], {"ver": 1, "body": {"div": [4,0]}}, True) print(ctx) print(list(ctx.out_string)) assert ctx.out_document == {"ver": 1, "fault": { 'faultcode': 'Server', 'faultstring': 'Internal Error'}} class TestJsonDocument(unittest.TestCase): def test_out_kwargs(self): class SomeService(Service): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument()) assert 'cls' in app.out_protocol.kwargs assert not ('cls' in app.in_protocol.kwargs) app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument(cls='hey')) assert app.out_protocol.kwargs['cls'] == 'hey' assert not ('cls' in app.in_protocol.kwargs) def test_invalid_input(self): class SomeService(Service): pass app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [b'{'] ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') assert ctx.in_error.faultcode == 'Client.JsonDecodeError' class TestJsonP(unittest.TestCase): def test_callback_name(self): callback_name = 'some_callback' class SomeComplexModel(ComplexModel): i = Integer s = Unicode v1 = 42 v2 = SomeComplexModel(i=42, s='foo') class SomeService(Service): @srpc(_returns=Integer) def yay(): return v1 @srpc(_returns=SomeComplexModel) def complex(): return v2 app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonP(callback_name)) server = NullServer(app, ostr=True) ret = server.service.yay() ret = list(ret) print(b''.join(ret)) assert b''.join(ret) == b''.join((callback_name.encode('utf8'), b'(', str(v1).encode('utf8'), b');')) ret = server.service.complex() ret = list(ret) print(b''.join(ret)) assert b''.join(ret) == b''.join((callback_name.encode('utf8'), b'(', json.dumps({"i": 42, "s": "foo"}).encode('utf-8') , b');')) def test_wrapped_array_in_wrapped_response(self): from spyne.model.complex import ComplexModel, Array from spyne.model.primitive import Unicode class Permission(ComplexModel): _type_info = [ ('application', Unicode), ('feature', Unicode), ] class SomeService(Service): @srpc(_returns=Array(Permission)) def yay(): return [ Permission(application='app', feature='f1'), Permission(application='app', feature='f2') ] app = Application([SomeService], 'tns', in_protocol=JsonDocument(), out_protocol=JsonDocument(ignore_wrappers=False)) server = NullServer(app, ostr=True) retstr = b''.join(server.service.yay()).decode('utf-8') print(retstr) assert retstr == '{"yayResponse": {"yayResult": [' \ '{"Permission": {"application": "app", "feature": "f1"}}, ' \ '{"Permission": {"application": "app", "feature": "f2"}}]}}' if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_msgpack.py000077500000000000000000000110231417664205300231140ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging from spyne.util import six logging.basicConfig(level=logging.DEBUG) import unittest import msgpack from spyne import MethodContext from spyne.application import Application from spyne.decorator import rpc from spyne.decorator import srpc from spyne.service import Service from spyne.model.complex import Array from spyne.model.primitive import String from spyne.model.complex import ComplexModel from spyne.model.primitive import Unicode from spyne.protocol.msgpack import MessagePackDocument from spyne.protocol.msgpack import MessagePackRpc from spyne.util.six import BytesIO from spyne.server import ServerBase from spyne.server.wsgi import WsgiApplication from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.test.test_service import start_response def convert_dict(d): if isinstance(d, six.text_type): return d.encode('utf8') if not isinstance(d, dict): return d r = {} for k, v in d.items(): r[k.encode('utf8')] = convert_dict(v) return r # apply spyne defaults to test unpacker TestMessagePackDocument = TDictDocumentTest(msgpack, MessagePackDocument, loads_kwargs=dict(use_list=False), convert_dict=convert_dict) class TestMessagePackRpc(unittest.TestCase): def test_invalid_input(self): class SomeService(Service): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [b'\xdf'] # Invalid input ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error.faultcode == 'Client.MessagePackDecodeError' def test_rpc(self): data = {"a":"b", "c": "d"} class KeyValuePair(ComplexModel): key = Unicode value = Unicode class SomeService(Service): @rpc(String(max_occurs='unbounded'), _returns=Array(KeyValuePair), _in_variable_names={ 'keys': 'key' } ) def get_values(ctx, keys): for k in keys: yield KeyValuePair(key=k, value=data[k]) application = Application([SomeService], in_protocol=MessagePackRpc(), out_protocol=MessagePackRpc(ignore_wrappers=False), name='Service', tns='tns') server = WsgiApplication(application) input_string = msgpack.packb([0, 0, "get_values", [["a", "c"]]]) input_stream = BytesIO(input_string) ret = server({ 'CONTENT_LENGTH': str(len(input_string)), 'CONTENT_TYPE': 'application/x-msgpack', 'HTTP_CONNECTION': 'close', 'HTTP_CONTENT_LENGTH': str(len(input_string)), 'HTTP_CONTENT_TYPE': 'application/x-msgpack', 'PATH_INFO': '/', 'QUERY_STRING': '', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '7000', 'REQUEST_METHOD': 'POST', 'wsgi.url_scheme': 'http', 'wsgi.input': input_stream, }, start_response) ret = b''.join(ret) print(repr(ret)) ret = msgpack.unpackb(ret) print(repr(ret)) s = [1, 0, None, {b'get_valuesResponse': { b'get_valuesResult': [ {b"KeyValuePair": {b'key': b'a', b'value': b'b'}}, {b"KeyValuePair": {b'key': b'c', b'value': b'd'}}, ] }} ] print(s) assert ret == s if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_soap11.py000077500000000000000000000402401417664205300225760ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # # Most of the service tests are performed through the interop tests. # import datetime import unittest from lxml import etree import pytz from spyne import MethodContext from spyne.application import Application from spyne.decorator import rpc from spyne.interface.wsdl import Wsdl11 from spyne.model.complex import Array from spyne.model.complex import ComplexModel from spyne.model.primitive import Unicode from spyne.model.primitive import DateTime, Date from spyne.model.primitive import Float from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.model.fault import Fault from spyne.protocol.soap import Soap11 from spyne.service import Service from spyne.server import ServerBase from spyne.protocol.soap import _from_soap from spyne.protocol.soap import _parse_xml_string Application.transport = 'test' def start_response(code, headers): print(code, headers) class Address(ComplexModel): __namespace__ = "TestService" street = String city = String zip = Integer since = DateTime laditude = Float longitude = Float class Person(ComplexModel): __namespace__ = "TestService" name = String birthdate = DateTime age = Integer addresses = Array(Address) titles = Array(String) class Request(ComplexModel): __namespace__ = "TestService" param1 = String param2 = Integer class Response(ComplexModel): __namespace__ = "TestService" param1 = Float class TypeNS1(ComplexModel): __namespace__ = "TestService.NS1" s = String i = Integer class TypeNS2(ComplexModel): __namespace__ = "TestService.NS2" d = DateTime f = Float class MultipleNamespaceService(Service): @rpc(TypeNS1, TypeNS2) def a(ctx, t1, t2): return "OK" class TestService(Service): @rpc(String, _returns=String) def aa(ctx, s): return s @rpc(String, Integer, _returns=DateTime) def a(ctx, s, i): return datetime.datetime.now() @rpc(Person, String, Address, _returns=Address) def b(ctx, p, s, a): return Address() @rpc(Person) def d(ctx, Person): pass @rpc(Person) def e(ctx, Person): pass @rpc(String, String, String, _returns=String, _in_variable_names={'_from': 'from', '_self': 'self', '_import': 'import'}, _out_variable_name="return") def f(ctx, _from, _self, _import): return '1234' class MultipleReturnService(Service): @rpc(String, _returns=(String, String, String)) def multi(ctx, s): return s, 'a', 'b' class TestSingle(unittest.TestCase): def setUp(self): self.app = Application([TestService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'null.spyne' self.srv = TestService() wsdl = Wsdl11(self.app.interface) wsdl.build_interface_document('URL') self.wsdl_str = wsdl.get_interface_document() self.wsdl_doc = etree.fromstring(self.wsdl_str) def test_portypes(self): porttype = self.wsdl_doc.find('{http://schemas.xmlsoap.org/wsdl/}portType') self.assertEqual( len(self.srv.public_methods), len(porttype.getchildren())) def test_override_param_names(self): for n in [b'self', b'import', b'return', b'from']: assert n in self.wsdl_str, '"%s" not in self.wsdl_str' class TestReturn(unittest.TestCase): def setUp(self): self.app = Application([MultipleReturnService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) self.app.transport = 'none' self.wsdl = Wsdl11(self.app.interface) self.wsdl.build_interface_document('URL') def test_multiple_return(self): message_class = list(MultipleReturnService.public_methods.values())[0].out_message message = message_class() self.assertEqual(len(message._type_info), 3) sent_xml = etree.Element('test') self.app.out_protocol.to_parent(None, message_class, ('a', 'b', 'c'), sent_xml, self.app.tns) sent_xml = sent_xml[0] print((etree.tostring(sent_xml, pretty_print=True))) response_data = self.app.out_protocol.from_element(None, message_class, sent_xml) self.assertEqual(len(response_data), 3) self.assertEqual(response_data[0], 'a') self.assertEqual(response_data[1], 'b') self.assertEqual(response_data[2], 'c') class TestSoap11(unittest.TestCase): def test_simple_message(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'s': String, 'i': Integer} ) m.resolve_namespace(m, 'test') m_inst = m(s="a", i=43) e = etree.Element('test') Soap11().to_parent(None, m, m_inst, e, m.get_namespace()) e=e[0] self.assertEqual(e.tag, '{%s}myMessage' % m.get_namespace()) self.assertEqual(e.find('{%s}s' % m.get_namespace()).text, 'a') self.assertEqual(e.find('{%s}i' % m.get_namespace()).text, '43') values = Soap11().from_element(None, m, e) self.assertEqual('a', values.s) self.assertEqual(43, values.i) def test_href(self): # the template. Start at pos 0, some servers complain if # xml tag is not in the first line. envelope_string = [ b''' somemachine someuser machine2 user2 '''] root, xmlids = _parse_xml_string(envelope_string, etree.XMLParser(), 'utf8') header, payload = _from_soap(root, xmlids) # quick and dirty test href reconstruction self.assertEqual(len(payload[0]), 2) def test_namespaces(self): m = ComplexModel.produce( namespace="some_namespace", type_name='myMessage', members={'s': String, 'i': Integer}, ) mi = m() mi.s = 'a' e = etree.Element('test') Soap11().to_parent(None, m, mi, e, m.get_namespace()) e=e[0] self.assertEqual(e.tag, '{some_namespace}myMessage') def test_class_to_parent(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'p': Person} ) m.resolve_namespace(m, "punk") m_inst = m() m_inst.p = Person() m_inst.p.name = 'steve-o' m_inst.p.age = 2 m_inst.p.addresses = [] element=etree.Element('test') Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) element=element[0] self.assertEqual(element.tag, '{%s}myMessage' % m.get_namespace()) self.assertEqual(element[0].find('{%s}name' % Person.get_namespace()).text, 'steve-o') self.assertEqual(element[0].find('{%s}age' % Person.get_namespace()).text, '2') self.assertEqual( len(element[0].find('{%s}addresses' % Person.get_namespace())), 0) p1 = Soap11().from_element(None, m, element)[0] self.assertEqual(p1.name, m_inst.p.name) self.assertEqual(p1.age, m_inst.p.age) self.assertEqual(p1.addresses, []) def test_datetime_fixed_format(self): # Soap should ignore formats n = datetime.datetime.now(pytz.utc).replace(microsecond=0) format = "%Y %m %d %H %M %S" element = etree.Element('test') Soap11().to_parent(None, DateTime(dt_format=format), n, element, 'some_namespace') assert element[0].text == n.isoformat() dt = Soap11().from_element(None, DateTime(dt_format=format), element[0]) assert n == dt def test_date_with_tzoffset(self): for iso_d in ('2013-04-05', '2013-04-05+02:00', '2013-04-05-02:00', '2013-04-05Z'): d = Soap11().from_unicode(Date, iso_d) assert isinstance(d, datetime.date) == True assert d.year == 2013 assert d.month == 4 assert d.day == 5 def test_to_parent_nested(self): m = ComplexModel.produce( namespace=None, type_name='myMessage', members={'p':Person} ) m.resolve_namespace(m, "m") p = Person() p.name = 'steve-o' p.age = 2 p.addresses = [] for i in range(0, 100): a = Address() a.street = '123 happy way' a.zip = i a.laditude = '45.22' a.longitude = '444.234' p.addresses.append(a) m_inst = m(p=p) element=etree.Element('test') Soap11().to_parent(None, m, m_inst, element, m.get_namespace()) element=element[0] self.assertEqual('{%s}myMessage' % m.get_namespace(), element.tag) addresses = element[0].find('{%s}addresses' % Person.get_namespace()) self.assertEqual(100, len(addresses)) self.assertEqual('0', addresses[0].find('{%s}zip' % Address.get_namespace()).text) def test_fault_deserialization_missing_fault_actor(self): element = etree.fromstring(b""" soap:Client Some String Some_Policy """) ret = Soap11().from_element(None, Fault, element[0][0]) assert ret.faultcode == "soap:Client" # TestSoapHeader supporting classes. # SOAP Header Elements defined by WS-Addressing. NAMESPACE_ADDRESSING = 'http://www.w3.org/2005/08/addressing' class Action (Unicode): __type_name__ = "Action" __namespace__ = NAMESPACE_ADDRESSING class MessageID (Unicode): __type_name__ = "MessageID" __namespace__ = NAMESPACE_ADDRESSING class RelatesTo (Unicode): __type_name__ = "RelatesTo" __namespace__ = NAMESPACE_ADDRESSING class SOAPServiceWithHeader(Service): @rpc(Unicode, _in_header=(Action, MessageID, RelatesTo), _out_variable_name= 'status', _returns=Unicode ) def someRequest(ctx, response): print (response) return 'OK' class TestSoapHeader(unittest.TestCase): def setUp(self): self.app = Application([SOAPServiceWithHeader], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) def test_soap_input_header(self): server = ServerBase(self.app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b''' /SomeAction SomeMessageID SomeRelatesToID OK ''' ] ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') server.get_in_object(ctx) self.assertEqual(ctx.in_header[0], '/SomeAction') self.assertEqual(ctx.in_header[1], 'SomeMessageID') self.assertEqual(ctx.in_header[2], 'SomeRelatesToID') def test_soap_input_header_order(self): """ Tests supports for input headers whose elements are provided in different order than that defined in rpc declaration _in_header parameter. """ server = ServerBase(self.app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b''' SomeMessageID SomeRelatesToID /SomeAction OK ''' ] ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') server.get_in_object(ctx) self.assertEqual(ctx.in_header[0], '/SomeAction') self.assertEqual(ctx.in_header[1], 'SomeMessageID') self.assertEqual(ctx.in_header[2], 'SomeRelatesToID') def test_soap_input_header_order_and_missing(self): """ Test that header ordering logic also works when an input header element is missing. Confirm that it returns None for the missing parameter. """ server = ServerBase(self.app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b''' SomeMessageID /SomeAction OK ''' ] ctx, = server.generate_contexts(initial_ctx, in_string_charset='utf8') server.get_in_object(ctx) self.assertEqual(ctx.in_header[0], '/SomeAction') self.assertEqual(ctx.in_header[1], 'SomeMessageID') self.assertEqual(ctx.in_header[2], None) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_soap12.py000077500000000000000000000265061417664205300226100ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import unicode_literals import unittest from lxml import etree from lxml.doctestcompare import LXMLOutputChecker, PARSE_XML from spyne import Fault, Unicode, ByteArray from spyne.application import Application from spyne.const import xml as ns from spyne.const.xml import NS_SOAP11_ENV from spyne.decorator import srpc, rpc from spyne.interface import Wsdl11 from spyne.model.complex import ComplexModel from spyne.model.primitive import Integer, String from spyne.protocol.soap.mime import _join_attachment from spyne.protocol.soap.soap12 import Soap12 from spyne.protocol.xml import XmlDocument from spyne.server.wsgi import WsgiApplication from spyne.service import Service from spyne.test.protocol.test_soap11 import TestService, TestSingle, \ TestReturn, MultipleReturnService from spyne.util.six import BytesIO def start_response(code, headers): print(code, headers) MTOM_REQUEST = b""" --uuid:2e53e161-b47f-444a-b594-eb6b72e76997 Content-Type: application/xop+xml; charset=UTF-8; type="application/soap+xml"; action="sendDocument"; Content-Transfer-Encoding: binary Content-ID: EA055406-5881-4F02-A3DC-9A5A7510D018.dat 26981FCD51C95FA47780400B7A45132F --uuid:2e53e161-b47f-444a-b594-eb6b72e76997 Content-Type: application/octet-stream Content-Transfer-Encoding: binary Content-ID: <04dfbca1-54b8-4631-a556-4addea6716ed-223384@cxf.apache.org> sample data --uuid:2e53e161-b47f-444a-b594-eb6b72e76997-- """ # Service Classes class DownloadPartFileResult(ComplexModel): ErrorCode = Integer ErrorMessage = String Data = String class TestSingleSoap12(TestSingle): def setUp(self): self.app = Application([TestService], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) self.app.transport = 'null.spyne' self.srv = TestService() wsdl = Wsdl11(self.app.interface) wsdl.build_interface_document('URL') self.wsdl_str = wsdl.get_interface_document() self.wsdl_doc = etree.fromstring(self.wsdl_str) class TestMultipleSoap12(TestReturn): def setUp(self): self.app = Application([MultipleReturnService], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) self.app.transport = 'none' self.wsdl = Wsdl11(self.app.interface) self.wsdl.build_interface_document('URL') class TestSoap12(unittest.TestCase): def test_soap12(self): element = etree.fromstring(b""" env:Sender st:SomeDomainProblem Some_Policy """) so = Soap12() ret = so.from_element(None, Fault, element[0][0]) assert ret.faultcode == "env:Sender.st:SomeDomainProblem" def test_fault_generation(self): class SoapException(Service): @srpc() def soap_exception(): raise Fault( "Client.Plausible.issue", "A plausible fault", 'http://faultactor.example.com', detail={'some':'extra info'}) app = Application([SoapException], 'tns', in_protocol=Soap12(), out_protocol=Soap12()) req = b""" """ server = WsgiApplication(app) response = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'wsgi.input': BytesIO(req) }, start_response, "http://null"))) response_str = etree.tostring(response, pretty_print=True) print(response_str) expected = b""" soap12env:Sender Plausible issue A plausible fault http://faultactor.example.com extra info """ if not LXMLOutputChecker().check_output(expected, response_str, PARSE_XML): raise Exception("Got: %s but expected: %s" % (response_str, expected)) def test_gen_fault_codes(self): fault_string = "Server.Plausible.error" value, faultstrings = Soap12().gen_fault_codes(faultstring=fault_string) self.assertEqual(value, "%s:Receiver" %(Soap12.soap_env)) self.assertEqual(faultstrings[0], "Plausible") self.assertEqual(faultstrings[1], "error") fault_string = "UnknownFaultCode.Plausible.error" with self.assertRaises(TypeError): value, faultstrings = Soap12().gen_fault_codes(faultstring=fault_string) def test_mtom(self): FILE_NAME = 'EA055406-5881-4F02-A3DC-9A5A7510D018.dat' TNS = 'http://gib.gov.tr/vedop3/eFatura' class SomeService(Service): @rpc(Unicode(sub_name="fileName"), ByteArray(sub_name='binaryData'), ByteArray(sub_name="hash"), _returns=Unicode) def documentRequest(ctx, file_name, file_data, data_hash): assert file_name == FILE_NAME assert file_data == (b'sample data',) return file_name app = Application([SomeService], tns=TNS, in_protocol=Soap12(), out_protocol=Soap12()) server = WsgiApplication(app) response = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'Content-Type: multipart/related; ' 'type="application/xop+xml"; ' 'boundary="uuid:2e53e161-b47f-444a-b594-eb6b72e76997"; ' 'start=""; ' 'start-info="application/soap+xml"; action="sendDocument"', 'wsgi.input': BytesIO(MTOM_REQUEST.replace(b"\n", b"\r\n")) }, start_response, "http://null"))) response_str = etree.tostring(response, pretty_print=True) print(response_str) nsdict = dict(tns=TNS) assert etree.fromstring(response_str) \ .xpath(".//tns:documentRequestResult/text()", namespaces=nsdict) \ == [FILE_NAME] def test_mtom_join_envelope_chunks(self): FILE_NAME = 'EA055406-5881-4F02-A3DC-9A5A7510D018.dat' TNS = 'http://gib.gov.tr/vedop3/eFatura' # large enough payload to be chunked PAYLOAD = b"sample data " * 1024 class SomeService(Service): @rpc(Unicode(sub_name="fileName"), ByteArray(sub_name='binaryData'), ByteArray(sub_name="hash"), _returns=Unicode) def documentRequest(ctx, file_name, file_data, data_hash): assert file_name == FILE_NAME assert file_data == (PAYLOAD,) return file_name app = Application([SomeService], tns=TNS, in_protocol=Soap12(), out_protocol=Soap12()) server = WsgiApplication(app, block_length=1024) response = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'Content-Type: multipart/related; ' 'type="application/xop+xml"; ' 'boundary="uuid:2e53e161-b47f-444a-b594-eb6b72e76997"; ' 'start=""; ' 'start-info="application/soap+xml"; action="sendDocument"', 'wsgi.input': BytesIO(MTOM_REQUEST .replace(b"\n", b"\r\n") .replace(b"sample data", PAYLOAD)), }, start_response, "http://null"))) response_str = etree.tostring(response, pretty_print=True) print(response_str) nsdict = dict(tns=TNS) assert etree.fromstring(response_str) \ .xpath(".//tns:documentRequestResult/text()", namespaces=nsdict) \ == [FILE_NAME] def test_bytes_join_attachment(self): href_id = "http://tempuri.org/1/634133419330914808" payload = "ANJNSLJNDYBC SFDJNIREMX:CMKSAJN" envelope = ''' 0 ''' % href_id (joinedmsg, numreplaces) = _join_attachment(NS_SOAP11_ENV, href_id, envelope, payload) soaptree = etree.fromstring(joinedmsg) body = soaptree.find(ns.SOAP11_ENV("Body")) response = body.getchildren()[0] result = response.getchildren()[0] r = XmlDocument().from_element(None, DownloadPartFileResult, result) self.assertEqual(payload, r.Data) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_xml.py000077500000000000000000000524751417664205300223070ustar00rootroot00000000000000#!/usr/bin/env python # encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import print_function import logging logging.basicConfig(level=logging.DEBUG) import sys import unittest import decimal import datetime from pprint import pprint from base64 import b64encode from lxml import etree from lxml.builder import E from spyne import MethodContext, rpc, ByteArray, File, AnyXml, Ignored from spyne.context import FakeContext from spyne.const import RESULT_SUFFIX from spyne.service import Service from spyne.server import ServerBase from spyne.application import Application from spyne.decorator import srpc from spyne.util.six import BytesIO from spyne.model import Fault, Integer, Decimal, Unicode, Date, DateTime, \ XmlData, Array, ComplexModel, XmlAttribute, Mandatory as M from spyne.protocol.xml import XmlDocument, SchemaValidationError from spyne.util import six from spyne.util.xml import get_xml_as_object, get_object_as_xml, \ get_object_as_xml_polymorphic, get_xml_as_object_polymorphic from spyne.server.wsgi import WsgiApplication from spyne.const.xml import NS_XSI class TestXml(unittest.TestCase): def test_empty_string(self): class a(ComplexModel): b = Unicode elt = etree.fromstring('') o = get_xml_as_object(elt, a) assert o.b == '' def test_ignored(self): d = decimal.Decimal('1e100') class SomeService(Service): @srpc(Decimal(120,4), _returns=Decimal) def some_call(p): print(p) print(type(p)) assert type(p) == decimal.Decimal assert d == p return Ignored(p) app = Application([SomeService], "tns", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b'

', str(d).encode('ascii'), b'

' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) elt = etree.fromstring(b''.join(ctx.out_string)) logging.info(etree.tostring(elt, pretty_print=True).decode('utf8')) assert 0 == len(list(elt)) def test_xml_data(self): class C(ComplexModel): a = XmlData(Unicode) b = XmlAttribute(Unicode) class SomeService(Service): @srpc(C, _returns=C) def some_call(c): assert c.a == 'a' assert c.b == 'b' return c app = Application([SomeService], "tns", name="test_xml_data", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b'' b'a' b'' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) print(ctx.out_string) pprint(app.interface.nsmap) ret = etree.fromstring(b''.join(ctx.out_string)).xpath( '//tns:some_call' + RESULT_SUFFIX, namespaces=app.interface.nsmap)[0] print(etree.tostring(ret, pretty_print=True)) assert ret.text == "a" assert ret.attrib['b'] == "b" def test_wrapped_array(self): parent = etree.Element('parent') val = ['a', 'b'] cls = Array(Unicode, namespace='tns') XmlDocument().to_parent(None, cls, val, parent, 'tns') print(etree.tostring(parent, pretty_print=True)) xpath = parent.xpath('//x:stringArray/x:string/text()', namespaces={'x': 'tns'}) assert xpath == val def test_simple_array(self): class cls(ComplexModel): __namespace__ = 'tns' s = Unicode(max_occurs='unbounded') val = cls(s=['a', 'b']) parent = etree.Element('parent') XmlDocument().to_parent(None, cls, val, parent, 'tns') print(etree.tostring(parent, pretty_print=True)) xpath = parent.xpath('//x:cls/x:s/text()', namespaces={'x': 'tns'}) assert xpath == val.s def test_decimal(self): d = decimal.Decimal('1e100') class SomeService(Service): @srpc(Decimal(120,4), _returns=Decimal) def some_call(p): print(p) print(type(p)) assert type(p) == decimal.Decimal assert d == p return p app = Application([SomeService], "tns", in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [ b'

', str(d).encode('ascii'), b'

' ] ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) server.get_out_object(ctx) server.get_out_string(ctx) elt = etree.fromstring(b''.join(ctx.out_string)) print(etree.tostring(elt, pretty_print=True)) target = elt.xpath('//tns:some_callResult/text()', namespaces=app.interface.nsmap)[0] assert target == str(d) def test_subs(self): from lxml import etree from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_object_as_xml m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = Integer b = Integer(sub_name="bb") c = Integer(sub_ns="cc") d = Integer(sub_ns="dd", sub_name="dd") elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath("s0:a/text()", namespaces=m) == ["1"] assert elt.xpath("s0:bb/text()", namespaces=m) == ["2"] assert elt.xpath("s2:c/text()", namespaces=m) == ["3"] assert elt.xpath("s3:dd/text()", namespaces=m) == ["4"] c = get_xml_as_object(elt, C) print(c) assert c.a == 1 assert c.b == 2 assert c.c == 3 assert c.d == 4 def test_sub_attributes(self): from lxml import etree from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_object_as_xml m = { "s0": "aa", "s2": "cc", "s3": "dd", } class C(ComplexModel): __namespace__ = "aa" a = XmlAttribute(Integer) b = XmlAttribute(Integer(sub_name="bb")) c = XmlAttribute(Integer(sub_ns="cc")) d = XmlAttribute(Integer(sub_ns="dd", sub_name="dd")) elt = get_object_as_xml(C(a=1, b=2, c=3, d=4), C) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath("//*/@a") == ["1"] assert elt.xpath("//*/@bb") == ["2"] assert elt.xpath("//*/@s2:c", namespaces=m) == ["3"] assert elt.xpath("//*/@s3:dd", namespaces=m) == ["4"] c = get_xml_as_object(elt, C) print(c) assert c.a == 1 assert c.b == 2 assert c.c == 3 assert c.d == 4 def test_dates(self): d = Date xml_dates = [ etree.fromstring(b'2013-04-05'), etree.fromstring(b'2013-04-05+02:00'), etree.fromstring(b'2013-04-05-02:00'), etree.fromstring(b'2013-04-05Z'), ] for xml_date in xml_dates: c = get_xml_as_object(xml_date, d) assert isinstance(c, datetime.date) == True assert c.year == 2013 assert c.month == 4 assert c.day == 5 def test_datetime_usec(self): fs = etree.fromstring d = get_xml_as_object(fs('2013-04-05T06:07:08.123456'), DateTime) assert d.microsecond == 123456 # rounds up d = get_xml_as_object(fs('2013-04-05T06:07:08.1234567'), DateTime) assert d.microsecond == 123457 # rounds down d = get_xml_as_object(fs('2013-04-05T06:07:08.1234564'), DateTime) assert d.microsecond == 123456 # rounds up as well d = get_xml_as_object(fs('2013-04-05T06:07:08.1234565'), DateTime) # FIXME: this is very interesting. why? if not six.PY2: assert d.microsecond == 123456 else: assert d.microsecond == 123457 def _get_ctx(self, server, in_string): initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = in_string ctx, = server.generate_contexts(initial_ctx) server.get_in_object(ctx) return ctx def test_mandatory_elements(self): class SomeService(Service): @srpc(M(Unicode), _returns=Unicode) def some_call(s): assert s == 'hello' return s app = Application([SomeService], "tns", name="test_mandatory_elements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) # Valid call with all mandatory elements in ctx = self._get_ctx(server, [ b'' b'hello' b'' ]) server.get_out_object(ctx) server.get_out_string(ctx) ret = etree.fromstring(b''.join(ctx.out_string)).xpath( '//tns:some_call%s/text()' % RESULT_SUFFIX, namespaces=app.interface.nsmap)[0] assert ret == 'hello' # Invalid call ctx = self._get_ctx(server, [ b'' # no mandatory elements here... b'' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) def test_unicode_chars_in_exception(self): class SomeService(Service): @srpc(Unicode(pattern=u'x'), _returns=Unicode) def some_call(s): test(should, never, reach, here) app = Application([SomeService], "tns", name="test_mandatory_elements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = WsgiApplication(app) req = ( u'' u'Ğ' u'' ).encode('utf8') print("AAA") resp = server({ 'QUERY_STRING': '', 'PATH_INFO': '/', 'REQUEST_METHOD': 'POST', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'wsgi.input': BytesIO(req), "wsgi.url_scheme": 'http', }, lambda x, y: print(x,y)) print("AAA") assert u'Ğ'.encode('utf8') in b''.join(resp) def test_mandatory_subelements(self): class C(ComplexModel): foo = M(Unicode) class SomeService(Service): @srpc(C.customize(min_occurs=1), _returns=Unicode) def some_call(c): assert c is not None assert c.foo == 'hello' return c.foo app = Application( [SomeService], "tns", name="test_mandatory_subelements", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) ctx = self._get_ctx(server, [ b'' # no mandatory elements at all... b'' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) ctx = self._get_ctx(server, [ b'' b'' # no mandatory elements here... b'' b'' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) def test_mandatory_element_attributes(self): class C(ComplexModel): bar = XmlAttribute(M(Unicode)) class SomeService(Service): @srpc(C.customize(min_occurs=1), _returns=Unicode) def some_call(c): assert c is not None assert hasattr(c, 'foo') assert c.foo == 'hello' return c.foo app = Application( [SomeService], "tns", name="test_mandatory_element_attributes", in_protocol=XmlDocument(validator='lxml'), out_protocol=XmlDocument()) server = ServerBase(app) ctx = self._get_ctx(server, [ b'' # no mandatory elements at all... b'' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) ctx = self._get_ctx(server, [ b'' b'' # no mandatory elements here... b'' b'' ]) self.assertRaises(SchemaValidationError, server.get_out_object, ctx) def test_bare_sub_name_ns(self): class Action(ComplexModel): class Attributes(ComplexModel.Attributes): sub_ns = "SOME_NS" sub_name = "Action" data = XmlData(Unicode) must_understand = XmlAttribute(Unicode) elt = get_object_as_xml(Action("x", must_understand="y"), Action) eltstr = etree.tostring(elt) print(eltstr) assert eltstr == b'x' def test_null_mandatory_attribute(self): class Action (ComplexModel): data = XmlAttribute(M(Unicode)) elt = get_object_as_xml(Action(), Action) eltstr = etree.tostring(elt) print(eltstr) assert eltstr == b'' def test_bytearray(self): v = b'aaaa' elt = get_object_as_xml([v], ByteArray, 'B') eltstr = etree.tostring(elt) print(eltstr) assert elt.text == b64encode(v).decode('ascii') def test_any_xml_text(self): v = u"" elt = get_object_as_xml(v, AnyXml, 'B', no_namespace=True) eltstr = etree.tostring(elt) print(eltstr) assert etree.tostring(elt[0], encoding="unicode") == v def test_any_xml_bytes(self): v = b"" elt = get_object_as_xml(v, AnyXml, 'B', no_namespace=True) eltstr = etree.tostring(elt) print(eltstr) assert etree.tostring(elt[0]) == v def test_any_xml_elt(self): v = E.roots(E.bloody(E.roots())) elt = get_object_as_xml(v, AnyXml, 'B') eltstr = etree.tostring(elt) print(eltstr) assert etree.tostring(elt[0]) == etree.tostring(v) def test_file(self): v = b'aaaa' f = BytesIO(v) elt = get_object_as_xml(File.Value(handle=f), File, 'B') eltstr = etree.tostring(elt) print(eltstr) assert elt.text == b64encode(v).decode('ascii') def test_fault_detail_as_dict(self): elt = get_object_as_xml(Fault(detail={"this": "that"}), Fault) eltstr = etree.tostring(elt) print(eltstr) assert b'that' in eltstr def test_xml_encoding(self): ctx = FakeContext(out_document=E.rain(u"yağmur")) XmlDocument(encoding='iso-8859-9').create_out_string(ctx) s = b''.join(ctx.out_string) assert u"ğ".encode('iso-8859-9') in s def test_default(self): class SomeComplexModel(ComplexModel): _type_info = [ ('a', Unicode), ('b', Unicode(default='default')), ] obj = XmlDocument().from_element( None, SomeComplexModel, etree.fromstring(""" string """) ) # xml schema says it should be None assert obj.b == 'default' obj = XmlDocument().from_element( None, SomeComplexModel, etree.fromstring(""" string """ % NS_XSI) ) # xml schema says it should be 'default' assert obj.b == 'default' obj = XmlDocument(replace_null_with_default=False).from_element( None, SomeComplexModel, etree.fromstring(""" string """ % NS_XSI) ) # xml schema says it should be 'default' assert obj.b is None def test_polymorphic_roundtrip(self): class B(ComplexModel): __namespace__ = 'some_ns' _type_info = { '_b': Unicode, } def __init__(self): super(B, self).__init__() self._b = "b" class C(B): __namespace__ = 'some_ns' _type_info = { '_c': Unicode, } def __init__(self): super(C, self).__init__() self._c = "c" class A(ComplexModel): __namespace__ = 'some_ns' _type_info = { '_a': Unicode, '_b': B, } def __init__(self, b=None): super(A, self).__init__() self._a = 'a' self._b = b a = A(b=C()) elt = get_object_as_xml_polymorphic(a, A) xml_string = etree.tostring(elt, pretty_print=True) if six.PY2: print(xml_string, end="") else: sys.stdout.buffer.write(xml_string) element_tree = etree.fromstring(xml_string) new_a = get_xml_as_object_polymorphic(elt, A) assert new_a._a == a._a, (a._a, new_a._a) assert new_a._b._b == a._b._b, (a._b._b, new_a._b._b) assert new_a._b._c == a._b._c, (a._b._c, new_a._b._c) class TestIncremental(unittest.TestCase): def test_one(self): class SomeComplexModel(ComplexModel): s = Unicode i = Integer v = SomeComplexModel(s='a', i=1), class SomeService(Service): @rpc(_returns=SomeComplexModel) def get(ctx): return v desc = SomeService.public_methods['get'] ctx = FakeContext(out_object=v, descriptor=desc) ostr = ctx.out_stream = BytesIO() XmlDocument(Application([SomeService], __name__)) \ .serialize(ctx, XmlDocument.RESPONSE) elt = etree.fromstring(ostr.getvalue()) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('x:getResult/x:i/text()', namespaces={'x':__name__}) == ['1'] assert elt.xpath('x:getResult/x:s/text()', namespaces={'x':__name__}) == ['a'] def test_many(self): class SomeComplexModel(ComplexModel): s = Unicode i = Integer v = [ SomeComplexModel(s='a', i=1), SomeComplexModel(s='b', i=2), SomeComplexModel(s='c', i=3), SomeComplexModel(s='d', i=4), SomeComplexModel(s='e', i=5), ] class SomeService(Service): @rpc(_returns=Array(SomeComplexModel)) def get(ctx): return v desc = SomeService.public_methods['get'] ctx = FakeContext(out_object=[v], descriptor=desc) ostr = ctx.out_stream = BytesIO() XmlDocument(Application([SomeService], __name__)) \ .serialize(ctx, XmlDocument.RESPONSE) elt = etree.fromstring(ostr.getvalue()) print(etree.tostring(elt, pretty_print=True)) assert elt.xpath('x:getResult/x:SomeComplexModel/x:i/text()', namespaces={'x': __name__}) == ['1', '2', '3', '4', '5'] assert elt.xpath('x:getResult/x:SomeComplexModel/x:s/text()', namespaces={'x': __name__}) == ['a', 'b', 'c', 'd', 'e'] if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/protocol/test_yaml.py000077500000000000000000000035721417664205300224430ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import unittest from spyne.test.protocol._test_dictdoc import TDictDocumentTest from spyne.protocol.yaml import YamlDocument from spyne import MethodContext from spyne.application import Application from spyne.decorator import srpc from spyne.service import Service from spyne.server import ServerBase from spyne.protocol.yaml import yaml yaml.dumps = yaml.dump yaml.loads = yaml.load TestYamlDocument = TDictDocumentTest(yaml, YamlDocument, YamlDocument().out_kwargs) class Test(unittest.TestCase): def test_invalid_input(self): class SomeService(Service): @srpc() def yay(): pass app = Application([SomeService], 'tns', in_protocol=YamlDocument(), out_protocol=YamlDocument()) server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [b'{'] ctx, = server.generate_contexts(initial_ctx) assert ctx.in_error.faultcode == 'Client.YamlDecodeError' if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/regen_wsdl.py000077500000000000000000000015611417664205300207260ustar00rootroot00000000000000#!/usr/bin/env python from lxml import etree from spyne.test.sort_wsdl import sort_wsdl from spyne.interface.wsdl import Wsdl11 from spyne.test.interop.server._service import services from spyne.application import Application app = Application(services, 'spyne.test.interop.server') app.transport = 'http://schemas.xmlsoap.org/soap/http' wsdl = Wsdl11(app.interface) wsdl.build_interface_document('http://localhost:9754/') elt = etree.ElementTree(etree.fromstring(wsdl.get_interface_document())) sort_wsdl(elt) s = etree.tostring(elt) # minidom's serialization seems to put attributes in alphabetic order. # this is exactly what we want here. from xml.dom.minidom import parseString doc = parseString(s) s = doc.toprettyxml(indent=' ', newl='\n', encoding='utf8') s = s.replace(" xmlns:","\n xmlns:") open('wsdl.xml', 'w').write(s) print('wsdl.xml written') spyne-spyne-2.14.0/spyne/test/sort_wsdl.py000077500000000000000000000054151417664205300206170ustar00rootroot00000000000000#!/usr/bin/python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Quick hack to sort the wsdl. it's helpful when comparing the wsdl output from two spyne versions. """ ns_wsdl = "http://schemas.xmlsoap.org/wsdl/" ns_schema = "http://www.w3.org/2001/XMLSchema" import sys from lxml import etree def cache_order(l, ns): return dict([ ("{%s}%s" % (ns, a), l.index(a)) for a in l]) wsdl_order = ('types', 'message', 'service', 'portType', 'binding') wsdl_order = cache_order(wsdl_order, ns_wsdl) schema_order = ('import', 'element', 'simpleType', 'complexType', 'attribute') schema_order = cache_order(schema_order, ns_schema) parser = etree.XMLParser(remove_blank_text=True) def main(): tree = etree.parse(sys.stdin, parser=parser) sort_wsdl(tree) tree.write(sys.stdout, encoding="UTF-8", xml_declaration=True) return 0 def sort_wsdl(tree): l0 = [] type_node = None for e in tree.getroot(): if e.tag == "{%s}types" % ns_wsdl: assert type_node is None type_node = e else: l0.append(e) e.getparent().remove(e) l0.sort(key=lambda e: (wsdl_order[e.tag], e.attrib['name'])) for e in l0: tree.getroot().append(e) for e in tree.getroot(): if e.tag in ("{%s}portType" % ns_wsdl, "{%s}binding" % ns_wsdl, "{%s}operation" % ns_wsdl): nodes = [] for p in e.getchildren(): nodes.append(p) nodes.sort(key=lambda e: e.attrib.get('name', '0')) for p in nodes: e.append(p) schemas = [] for e in type_node: schemas.append(e) e.getparent().remove(e) schemas.sort(key=lambda e: e.attrib["targetNamespace"]) for s in schemas: type_node.append(s) for s in schemas: nodes = [] for e in s: nodes.append(e) e.getparent().remove(e) nodes.sort(key=lambda e: (schema_order[e.tag], e.attrib.get('name', '\0'))) for e in nodes: s.append(e) if __name__ == '__main__': sys.exit(main()) spyne-spyne-2.14.0/spyne/test/test_null_server.py000077500000000000000000000127111417664205300221730ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import gc import unittest from lxml import etree from spyne import Ignored from spyne import const from spyne.interface.wsdl import Wsdl11 from spyne.protocol.xml import XmlDocument from spyne.model.complex import Array from spyne.model.primitive import Boolean from spyne.model.primitive import String from spyne.application import Application from spyne.decorator import srpc from spyne.service import Service from spyne.server.null import NullServer class TestNullServer(unittest.TestCase): def test_empty_return_type(self): class MessageService(Service): @srpc(String) def send_message(s): return s application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) assert None == NullServer(application).service.send_message("zabaaa") def test_ignored(self): class MessageService(Service): @srpc(String, _returns=String) def send_message_1(s): return Ignored("xyz") @srpc(String) def send_message_2(s): return Ignored("xyz") @srpc(String, _returns=String) def send_message_3(s): return "OK" application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) assert Ignored("xyz") == server.service.send_message_1("zabaaa") assert Ignored("xyz") == server.service.send_message_2("zabaaa") assert "OK" == server.service.send_message_3("zabaaa") def test_call_one_arg(self): queue = set() class MessageService(Service): @srpc(String) def send_message(s): queue.add(s) application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) server.service.send_message("zabaaa") assert set(["zabaaa"]) == queue def test_call_two_args(self): queue = set() class MessageService(Service): @srpc(String, String) def send_message(s, k): queue.add((s,k)) application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) queue.clear() server.service.send_message("zabaaa", k="hobaa") assert set([("zabaaa","hobaa")]) == queue queue.clear() server.service.send_message(k="hobaa") assert set([(None,"hobaa")]) == queue queue.clear() server.service.send_message("zobaaa", s="hobaa") assert set([("hobaa", None)]) == queue def test_ostr(self): queue = set() class MessageService(Service): @srpc(String, String, _returns=Array(String)) def send_message(s, k): queue.add((s, k)) return [s, k] application = Application([MessageService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) ostr_server = NullServer(application, ostr=True) queue.clear() ret = ostr_server.service.send_message("zabaaa", k="hobaa") assert set([("zabaaa","hobaa")]) == queue assert etree.fromstring(b''.join(ret)).xpath('//tns:string/text()', namespaces=application.interface.nsmap) == ['zabaaa', 'hobaa'] queue.clear() ostr_server.service.send_message(k="hobaa") assert set([(None,"hobaa")]) == queue queue.clear() ostr_server.service.send_message("zobaaa", s="hobaa") assert set([("hobaa", None)]) == queue def test_no_gc_collect(self): class PingService(Service): @srpc(_returns=Boolean) def ping(): return True application = Application( [PingService], 'some_tns', in_protocol=XmlDocument(), out_protocol=XmlDocument()) server = NullServer(application) origin_collect = gc.collect origin_MIN_GC_INTERVAL = const.MIN_GC_INTERVAL try: gc.collect = lambda : 1/0 with self.assertRaises(ZeroDivisionError): const.MIN_GC_INTERVAL = 0 server.service.ping() # No raise const.MIN_GC_INTERVAL = float('inf') server.service.ping() finally: gc.collect = origin_collect const.MIN_GC_INTERVAL = origin_MIN_GC_INTERVAL if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/test_service.py000077500000000000000000000406631417664205300213020ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # # Most of the service tests are performed through the interop tests. # import logging logging.basicConfig(level=logging.DEBUG) import unittest from spyne.util.six import BytesIO from lxml import etree from spyne import LogicError from spyne.const import RESPONSE_SUFFIX from spyne.model.primitive import NATIVE_MAP from spyne.service import Service from spyne.decorator import rpc, srpc from spyne.application import Application from spyne.auxproc.sync import SyncAuxProc from spyne.auxproc.thread import ThreadAuxProc from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.server.null import NullServer from spyne.server.wsgi import WsgiApplication from spyne.model import Array, SelfReference, Iterable, ComplexModel, String, \ Unicode Application.transport = 'test' def start_response(code, headers): print(code, headers) class MultipleMethods1(Service): @srpc(String) def multi(s): return "%r multi 1" % s class MultipleMethods2(Service): @srpc(String) def multi(s): return "%r multi 2" % s class TestEvents(unittest.TestCase): def test_method_exception(self): from spyne.protocol.xml import XmlDocument h = [0] def on_method_exception_object(ctx): assert ctx.out_error is not None from spyne.protocol.xml import SchemaValidationError assert isinstance(ctx.out_error, SchemaValidationError) logging.error("method_exception_object: %r", repr(ctx.out_error)) h[0] += 1 def on_method_exception_document(ctx): assert ctx.out_error is not None from spyne.protocol.xml import SchemaValidationError assert isinstance(ctx.out_error, SchemaValidationError) logging.error("method_exception_document: %r", etree.tostring(ctx.out_document)) h[0] += 1 class SomeService(Service): @rpc(Unicode(5)) def some_call(ctx, some_str): print(some_str) app = Application([SomeService], "some_tns", in_protocol=XmlDocument(validator='lxml'), out_protocol=Soap11()) app.event_manager.add_listener( "method_exception_object", on_method_exception_object) app.event_manager.add_listener( "method_exception_document", on_method_exception_document) # this shouldn't be called because: # 1. document isn't validated # 2. hence; document can't be parsed # 3. hence; document can't be mapped to a function # 4. hence; document can't be mapped to a service class # 5. hence; no handlers from the service class is invoked. # 6. hence; the h[0] == 2 check (instead of 3) SomeService.event_manager.add_listener( "method_exception_object", on_method_exception_object) wsgi_app = WsgiApplication(app) xml_request = b""" 123456 """ _ = b''.join(wsgi_app({ 'PATH_INFO': '/', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '7000', 'REQUEST_METHOD': 'POST', 'wsgi.url_scheme': 'http', 'wsgi.input': BytesIO(xml_request), }, start_response)) assert h[0] == 2 class TestMultipleMethods(unittest.TestCase): def test_single_method(self): try: Application([MultipleMethods1, MultipleMethods2], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) except ValueError: pass else: raise Exception('must fail.') def test_simple_aux_nullserver(self): data = [] class SomeService(Service): @srpc(String) def call(s): data.append(s) class AuxService(Service): __aux__ = SyncAuxProc() @srpc(String) def call(s): data.append(s) app = Application([SomeService, AuxService], 'tns', 'name', Soap11(), Soap11()) server = NullServer(app) server.service.call("hey") assert data == ['hey', 'hey'] def test_namespace_in_message_name(self): class S(Service): @srpc(String, _in_message_name='{tns}inMessageName') def call(s): pass app = Application([S], 'tns', 'name', Soap11(), Soap11()) def test_simple_aux_wsgi(self): data = [] class SomeService(Service): @srpc(String, _returns=String) def call(s): data.append(s) class AuxService(Service): __aux__ = SyncAuxProc() @srpc(String, _returns=String) def call(s): data.append(s) app = Application([SomeService, AuxService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) server({ 'QUERY_STRING': 's=hey', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'SERVER_NAME': 'localhost', 'wsgi.input': BytesIO(), }, start_response, "http://null") assert data == ['hey', 'hey'] def test_thread_aux_wsgi(self): import logging logging.basicConfig(level=logging.DEBUG) data = set() class SomeService(Service): @srpc(String, _returns=String) def call(s): data.add(s) class AuxService(Service): __aux__ = ThreadAuxProc() @srpc(String, _returns=String) def call(s): data.add(s + "aux") app = Application([SomeService, AuxService], 'tns', in_protocol=HttpRpc(), out_protocol=HttpRpc()) server = WsgiApplication(app) server({ 'QUERY_STRING': 's=hey', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'SERVER_NAME': 'localhost', 'wsgi.input': BytesIO(), }, start_response, "http://null") import time time.sleep(1) assert data == set(['hey', 'heyaux']) def test_mixing_primary_and_aux_methods(self): try: class SomeService(Service): @srpc(String, _returns=String, _aux=ThreadAuxProc()) def call(s): pass @srpc(String, _returns=String) def mall(s): pass except Exception: pass else: raise Exception("must fail with 'Exception: you can't mix aux and " "non-aux methods in a single service definition.'") def __run_service(self, service): app = Application([service], 'tns', in_protocol=HttpRpc(), out_protocol=Soap11()) server = WsgiApplication(app) return_string = b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/some_call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'SERVER_NAME': 'localhost', 'wsgi.input': BytesIO(b""), }, start_response, "http://null")) elt = etree.fromstring(return_string) print(etree.tostring(elt, pretty_print=True)) return elt, app.interface.nsmap def test_settings_headers_from_user_code(self): class RespHeader(ComplexModel): __namespace__ = 'tns' Elem1 = String # test header in service definition class SomeService(Service): __out_header__ = RespHeader @rpc() def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader' \ '/tns:Elem1/text()' assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' # test header in decorator class SomeService(Service): @rpc(_out_header=RespHeader) def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader/tns' \ ':Elem1/text()' assert elt.xpath(query, namespaces=nsmap)[0] == 'Test1' # test no header class SomeService(Service): @rpc() def some_call(ctx): ctx.out_header = RespHeader() ctx.out_header.Elem1 = 'Test1' elt, nsmap = self.__run_service(SomeService) query = '/soap11env:Envelope/soap11env:Header/tns:RespHeader' \ '/tns:Elem1/text()' assert len(elt.xpath(query, namespaces=nsmap)) == 0 class TestNativeTypes(unittest.TestCase): def test_native_types(self): for t in NATIVE_MAP: class SomeService(Service): @rpc(t) def some_call(ctx, arg): pass nt, = SomeService.public_methods['some_call'].in_message \ ._type_info.values() assert issubclass(nt, NATIVE_MAP[t]) def test_native_types_in_arrays(self): for t in NATIVE_MAP: class SomeService(Service): @rpc(Array(t)) def some_call(ctx, arg): pass nt, = SomeService.public_methods['some_call'].in_message \ ._type_info.values() nt, = nt._type_info.values() assert issubclass(nt, NATIVE_MAP[t]) class TestBodyStyle(unittest.TestCase): def test_soap_bare_empty_output(self): class SomeService(Service): @rpc(String, _body_style='bare') def some_call(ctx, s): assert s == 'abc' app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = b""" abc """ server = WsgiApplication(app) resp = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'SERVER_NAME': 'localhost', 'wsgi.input': BytesIO(req), }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert len(resp[0]) == 1 assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX assert len(resp[0][0]) == 0 def test_soap_bare_empty_input(self): class SomeService(Service): @rpc(_body_style='bare', _returns=String) def some_call(ctx): return 'abc' app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = b""" """ server = WsgiApplication(app) resp = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'SERVER_NAME': 'localhost', 'wsgi.input': BytesIO(req) }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX assert resp[0][0].text == 'abc' def test_soap_bare_empty_model_input_method_name(self): class EmptyRequest(ComplexModel): pass try: class SomeService(Service): @rpc(EmptyRequest, _body_style='bare', _returns=String) def some_call(ctx, request): return 'abc' except Exception: pass else: raise Exception("Must fail with exception: body_style='bare' does " "not allow empty model as param") def test_implicit_class_conflict(self): class someCallResponse(ComplexModel): __namespace__ = 'tns' s = String class SomeService(Service): @rpc(someCallResponse, _returns=String) def someCall(ctx, x): return ['abc', 'def'] try: Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) except ValueError as e: print(e) else: raise Exception("must fail.") def test_soap_bare_wrapped_array_output(self): class SomeService(Service): @rpc(_body_style='bare', _returns=Array(String)) def some_call(ctx): return ['abc', 'def'] app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) req = b""" """ server = WsgiApplication(app) resp = etree.fromstring(b''.join(server({ 'QUERY_STRING': '', 'PATH_INFO': '/call', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'text/xml; charset=utf8', 'wsgi.input': BytesIO(req) }, start_response, "http://null"))) print(etree.tostring(resp, pretty_print=True)) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert resp[0][0].tag == '{tns}some_call' + RESPONSE_SUFFIX assert resp[0][0][0].text == 'abc' assert resp[0][0][1].text == 'def' def test_array_iterable(self): class SomeService(Service): @rpc(Array(Unicode), Iterable(Unicode)) def some_call(ctx, a, b): pass app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11(cleanup_namespaces=True)) server = WsgiApplication(app) def test_invalid_self_reference(self): try: class SomeService(Service): @rpc(_returns=SelfReference) def method(ctx): pass except LogicError: pass else: raise Exception("Must fail with: " "'SelfReference can't be used inside @rpc and its ilk'") if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/test_soft_validation.py000077500000000000000000000145021417664205300230200ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # # Most of the service tests are performed through the interop tests. # import unittest from spyne.application import Application from spyne.decorator import srpc from spyne.error import ValidationError from spyne.service import Service from spyne.protocol.http import HttpRpc from spyne.protocol.soap import Soap11 from spyne.model.primitive import Integer from spyne.model.primitive import String from spyne.server import ServerBase from spyne.server.wsgi import WsgiApplication from spyne import MethodContext from spyne.server.wsgi import WsgiMethodContext Application.transport = 'test' class TestValidationString(unittest.TestCase): def test_min_len(self): StrictType = String(min_len=3) self.assertEqual(StrictType.validate_string(StrictType, 'aaa'), True) self.assertEqual(StrictType.validate_string(StrictType, 'a'), False) def test_max_len(self): StrictType = String(max_len=3) self.assertEqual(StrictType.validate_string(StrictType, 'a'), True) self.assertEqual(StrictType.validate_string(StrictType, 'aaa'), True) self.assertEqual(StrictType.validate_string(StrictType, 'aaaa'), False) def test_pattern(self): # Pattern match needs to be checked after the string is decoded, that's # why we need to use validate_native here. StrictType = String(pattern='[a-z]') self.assertEqual(StrictType.validate_native(StrictType, 'a'), True) self.assertEqual(StrictType.validate_native(StrictType, 'a1'), False) self.assertEqual(StrictType.validate_native(StrictType, '1'), False) class TestValidationInteger(unittest.TestCase): def test_lt(self): StrictType = Integer(lt=3) self.assertEqual(StrictType.validate_native(StrictType, 2), True) self.assertEqual(StrictType.validate_native(StrictType, 3), False) def test_le(self): StrictType = Integer(le=3) self.assertEqual(StrictType.validate_native(StrictType, 2), True) self.assertEqual(StrictType.validate_native(StrictType, 3), True) self.assertEqual(StrictType.validate_native(StrictType, 4), False) def test_gt(self): StrictType = Integer(gt=3) self.assertEqual(StrictType.validate_native(StrictType, 4), True) self.assertEqual(StrictType.validate_native(StrictType, 3), False) def test_ge(self): StrictType = Integer(ge=3) self.assertEqual(StrictType.validate_native(StrictType, 3), True) self.assertEqual(StrictType.validate_native(StrictType, 2), False) class TestHttpRpcSoftValidation(unittest.TestCase): def setUp(self): class SomeService(Service): @srpc(String(pattern='a')) def some_method(s): pass @srpc(String(pattern='a', max_occurs=2)) def some_other_method(s): pass self.application = Application([SomeService], in_protocol=HttpRpc(validator='soft'), out_protocol=Soap11(), name='Service', tns='tns', ) def __get_ctx(self, mn, qs): server = WsgiApplication(self.application) ctx = WsgiMethodContext(server, { 'QUERY_STRING': qs, 'PATH_INFO': '/%s' % mn, 'REQUEST_METHOD': "GET", 'SERVER_NAME': 'localhost', }, 'some-content-type') ctx, = server.generate_contexts(ctx) server.get_in_object(ctx) return ctx def test_http_rpc(self): ctx = self.__get_ctx('some_method', 's=1') self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_method', 's=a') self.assertEqual(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', 's=1') self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=1&s=2') self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=1&s=2&s=3') self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=a&s=a&s=a') self.assertEqual(ctx.in_error.faultcode, 'Client.ValidationError') ctx = self.__get_ctx('some_other_method', 's=a&s=a') self.assertEqual(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', 's=a') self.assertEqual(ctx.in_error, None) ctx = self.__get_ctx('some_other_method', '') self.assertEqual(ctx.in_error, None) class TestSoap11SoftValidation(unittest.TestCase): def test_basic(self): class SomeService(Service): @srpc(String(pattern='a')) def some_method(s): pass application = Application([SomeService], in_protocol=Soap11(validator='soft'), out_protocol=Soap11(), name='Service', tns='tns', ) server = ServerBase(application) ctx = MethodContext(server, MethodContext.SERVER) ctx.in_string = [u""" OK """] ctx, = server.generate_contexts(ctx) server.get_in_object(ctx) self.assertEqual(isinstance(ctx.in_error, ValidationError), True) if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/test_sqlalchemy.py000077500000000000000000001173641417664205300220070ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logging.basicConfig(level=logging.DEBUG) import inspect import unittest import sqlalchemy from pprint import pprint from sqlalchemy import create_engine from sqlalchemy import MetaData from sqlalchemy import Column from sqlalchemy import Table from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker from spyne import M, Any, Double from spyne.model import XmlAttribute, File, XmlData, ComplexModel, Array, \ Integer32, Unicode, Integer, Enum, TTableModel, DateTime, Boolean from spyne.model.binary import HybridFileStore from spyne.model.complex import xml from spyne.model.complex import table from spyne.store.relational import get_pk_columns from spyne.store.relational.document import PGJsonB, PGJson, PGFileJson, \ PGObjectJson TableModel = TTableModel() class TestSqlAlchemyTypeMappings(unittest.TestCase): def test_init(self): fn = inspect.stack()[0][3] from sqlalchemy.inspection import inspect as sqla_inspect class SomeClass1(TableModel): __tablename__ = "%s_%d" % (fn, 1) i = Integer32(pk=True) e = Unicode(32) from spyne.util.dictdoc import get_dict_as_object inst = get_dict_as_object(dict(i=4), SomeClass1) assert not sqla_inspect(inst).attrs.e.history.has_changes() def test_bool(self): fn = inspect.stack()[0][3] class SomeClass1(TableModel): __tablename__ = "%s_%d" % (fn, 1) i = Integer32(pk=True) b = Boolean assert isinstance(SomeClass1.Attributes.sqla_table.c.b.type, sqlalchemy.Boolean) class SomeClass2(TableModel): __tablename__ = "%s_%d" % (fn, 2) i = Integer32(pk=True) b = Boolean(store_as=int) assert isinstance(SomeClass2.Attributes.sqla_table.c.b.type, sqlalchemy.SmallInteger) def test_jsonb(self): fn = inspect.stack()[0][3] class SomeClass1(TableModel): __tablename__ = "%s_%d" % (fn, 1) i = Integer32(pk=True) a = Any(store_as='json') assert isinstance(SomeClass1.Attributes.sqla_table.c.a.type, PGJson) class SomeClass2(TableModel): __tablename__ = "%s_%d" % (fn, 2) i = Integer32(pk=True) a = Any(store_as='jsonb') assert isinstance(SomeClass2.Attributes.sqla_table.c.a.type, PGJsonB) class SomeClass3(TableModel): __tablename__ = "%s_%d" % (fn, 3) i = Integer32(pk=True) a = File(store_as=HybridFileStore("path", db_format='jsonb')) assert isinstance(SomeClass3.Attributes.sqla_table.c.a.type, PGFileJson) assert SomeClass3.Attributes.sqla_table.c.a.type.dbt == 'jsonb' def test_obj_json(self): fn = inspect.stack()[0][3] class SomeClass(ComplexModel): s = Unicode d = Double class SomeClass1(TableModel): __tablename__ = "%s_%d" % (fn, 1) _type_info = [ ('i', Integer32(pk=True)), ('a', Array(SomeClass, store_as='json')), ] assert isinstance(SomeClass1.Attributes.sqla_table.c.a.type, PGObjectJson) class SomeClass2(TableModel): __tablename__ = "%s_%d" % (fn, 2) i = Integer32(pk=True) a = SomeClass.customize(store_as='json') assert isinstance(SomeClass2.Attributes.sqla_table.c.a.type, PGObjectJson) class TestSqlAlchemySchema(unittest.TestCase): def setUp(self): logging.getLogger('sqlalchemy').setLevel(logging.DEBUG) self.engine = create_engine('sqlite:///:memory:') self.session = sessionmaker(bind=self.engine)() self.metadata = TableModel.Attributes.sqla_metadata = MetaData() self.metadata.bind = self.engine logging.info('Testing against sqlalchemy-%s', sqlalchemy.__version__) def test_obj_json_dirty(self): fn = inspect.stack()[0][3] class SomeClass(ComplexModel): s = Unicode d = Double class SomeClass1(TableModel): __tablename__ = "%s_%d" % (fn, 1) _type_info = [ ('i', Integer32(pk=True)), ('a', SomeClass.store_as('jsonb')), ] self.metadata.create_all() sc1 = SomeClass1(i=5, a=SomeClass(s="s", d=42.0)) self.session.add(sc1) self.session.commit() from sqlalchemy.orm.attributes import flag_modified # TODO: maybe do the flag_modified() on setitem? sc1.a.s = "ss" flag_modified(sc1, 'a') assert sc1 in self.session.dirty self.session.commit() assert sc1.a.s == "ss" # not implemented #sc1.a[0].s = "sss" #flag_modified(sc1.a[0], 's') #assert sc1.a[0] in self.session.dirty def test_schema(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True, autoincrement=False) s = Unicode(64, unique=True) i = Integer32(64, index=True) t = SomeClass.__table__ self.metadata.create_all() # not needed, just nice to see. assert t.c.id.primary_key == True assert t.c.id.autoincrement == False indexes = list(t.indexes) indexes.sort(key=lambda idx: idx.name) for idx in indexes: assert 'i' in idx.columns or 's' in idx.columns if 's' in idx.columns: assert idx.unique def test_colname_simple(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True, autoincrement=False) s = Unicode(64, sqla_column_args=dict(name='ss')) t = SomeClass.__table__ self.metadata.create_all() # not needed, just nice to see. assert 'ss' in t.c def test_colname_complex_table(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table', sqla_column_args=dict(name='oo')) t = SomeClass.__table__ self.metadata.create_all() # not needed, just nice to see. assert 'oo_id' in t.c def test_colname_complex_json(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='json', sqla_column_args=dict(name='oo')) t = SomeClass.__table__ self.metadata.create_all() # not needed, just nice to see. assert 'oo' in t.c def test_nested_sql(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = ( {"sqlite_autoincrement": True}, ) id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table') self.metadata.create_all() soc = SomeOtherClass(s='ehe') sc = SomeClass(o=soc) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) print(sc_db) assert sc_db.o.s == 'ehe' assert sc_db.o_id == 1 sc_db.o = None self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.o == None assert sc_db.o_id == None def test_nested_sql_array_as_table(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='table') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_multi_table(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=table(multi=True)) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_multi_table_with_backref(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=table(multi=True, backref='some_classes')) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() soc_db = self.session.query(SomeOtherClass).all() assert soc_db[0].some_classes[0].id == 1 assert soc_db[1].some_classes[0].id == 1 self.session.close() def test_nested_sql_array_as_xml(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='xml') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_nested_sql_array_as_xml_no_ns(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as=xml(no_ns=True)) self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_xml = self.session.connection() \ .execute("select others from some_class") .fetchall()[0][0] from lxml import etree assert etree.fromstring(sc_xml).tag == 'SomeOtherClassArray' self.session.close() def test_inheritance(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(SomeOtherClass): numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) self.metadata.create_all() sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(5) assert sc_db.numbers == [1, 2, 3, 4] self.session.close() sc_db = self.session.query(SomeOtherClass).get(5) assert sc_db.id == 5 try: sc_db.numbers except AttributeError: pass else: raise Exception("must fail") self.session.close() def test_inheritance_with_complex_fields(self): class Foo(TableModel): __tablename__ = 'foo' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class Bar(TableModel): __tablename__ = 'bar' __table_args__ = {"sqlite_autoincrement": True} __mapper_args__ = { 'polymorphic_on': 'type', 'polymorphic_identity': 'bar', 'with_polymorphic': '*', } id = Integer32(primary_key=True) s = Unicode(64) type = Unicode(6) foos = Array(Foo).store_as('table') class SubBar(Bar): __mapper_args__ = { 'polymorphic_identity': 'subbar', } i = Integer32 sqlalchemy.orm.configure_mappers() mapper_subbar = SubBar.Attributes.sqla_mapper mapper_bar = Bar.Attributes.sqla_mapper assert not mapper_subbar.concrete for inheriting in mapper_subbar.iterate_to_root(): if inheriting is not mapper_subbar \ and not (mapper_bar.relationships['foos'] is mapper_subbar.relationships['foos']): raise Exception("Thou shalt stop children relationships " "from overriding the ones in parent") def test_mixins_with_complex_fields(self): class Foo(TableModel): __tablename__ = 'foo' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class Bar(TableModel): __tablename__ = 'bar' __table_args__ = {"sqlite_autoincrement": True} __mixin__ = True __mapper_args__ = { 'polymorphic_on': 'type', 'polymorphic_identity': 'bar', 'with_polymorphic': '*', } id = Integer32(primary_key=True) s = Unicode(64) type = Unicode(6) foos = Array(Foo).store_as('table') class SubBar(Bar): __mapper_args__ = { 'polymorphic_identity': 'subbar', } i = Integer32 sqlalchemy.orm.configure_mappers() mapper_subbar = SubBar.Attributes.sqla_mapper mapper_bar = Bar.Attributes.sqla_mapper assert not mapper_subbar.concrete for inheriting in mapper_subbar.iterate_to_root(): if inheriting is not mapper_subbar \ and not (mapper_bar.relationships['foos'] is mapper_subbar.relationships['foos']): raise Exception("Thou shalt stop children relationships " "from overriding the ones in parent") def test_sqlalchemy_inheritance(self): # no spyne code is involved here. # this is just to test test the sqlalchemy behavior that we rely on. class Employee(object): def __init__(self, name): self.name = name def __repr__(self): return self.__class__.__name__ + " " + self.name class Manager(Employee): def __init__(self, name, manager_data): self.name = name self.manager_data = manager_data def __repr__(self): return ( self.__class__.__name__ + " " + self.name + " " + self.manager_data ) class Engineer(Employee): def __init__(self, name, engineer_info): self.name = name self.engineer_info = engineer_info def __repr__(self): return ( self.__class__.__name__ + " " + self.name + " " + self.engineer_info ) employees_table = Table('employees', self.metadata, Column('employee_id', sqlalchemy.Integer, primary_key=True), Column('name', sqlalchemy.String(50)), Column('manager_data', sqlalchemy.String(50)), Column('engineer_info', sqlalchemy.String(50)), Column('type', sqlalchemy.String(20), nullable=False), ) employee_mapper = mapper(Employee, employees_table, polymorphic_on=employees_table.c.type, polymorphic_identity='employee') manager_mapper = mapper(Manager, inherits=employee_mapper, polymorphic_identity='manager') engineer_mapper = mapper(Engineer, inherits=employee_mapper, polymorphic_identity='engineer') self.metadata.create_all() manager = Manager('name', 'data') self.session.add(manager) self.session.commit() self.session.close() assert self.session.query(Employee).with_polymorphic('*') \ .filter_by(employee_id=1) \ .one().type == 'manager' def test_inheritance_polymorphic_with_non_nullables_in_subclasses(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} __mapper_args__ = {'polymorphic_on': 't', 'polymorphic_identity': 1} id = Integer32(primary_key=True) t = Integer32(nillable=False) s = Unicode(64, nillable=False) class SomeClass(SomeOtherClass): __mapper_args__ = ( (), {'polymorphic_identity': 2}, ) i = Integer(nillable=False) self.metadata.create_all() assert SomeOtherClass.__table__.c.s.nullable == False # this should be nullable to let other classes be added. # spyne still checks this constraint when doing input validation. # spyne should generate a constraint to check this at database level as # well. assert SomeOtherClass.__table__.c.i.nullable == True soc = SomeOtherClass(s='s') self.session.add(soc) self.session.commit() soc_id = soc.id try: sc = SomeClass(i=5) self.session.add(sc) self.session.commit() except IntegrityError: self.session.rollback() else: raise Exception("Must fail with IntegrityError.") sc2 = SomeClass(s='s') # this won't fail. should it? self.session.add(sc2) self.session.commit() self.session.expunge_all() assert self.session.query(SomeOtherClass).with_polymorphic('*') \ .filter_by(id=soc_id).one().t == 1 self.session.close() def test_inheritance_polymorphic(self): class SomeOtherClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} __mapper_args__ = {'polymorphic_on': 't', 'polymorphic_identity': 1} id = Integer32(primary_key=True) s = Unicode(64) t = Integer32(nillable=False) class SomeClass(SomeOtherClass): __mapper_args__ = {'polymorphic_identity': 2} numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a')) self.metadata.create_all() sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4]) self.session.add(sc) self.session.commit() self.session.close() assert self.session.query(SomeOtherClass).with_polymorphic('*') \ .filter_by(id=5).one().t == 2 self.session.close() def test_nested_sql_array_as_json(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='json') self.metadata.create_all() soc1 = SomeOtherClass(s='ehe1') soc2 = SomeOtherClass(s='ehe2') sc = SomeClass(others=[soc1, soc2]) self.session.add(sc) self.session.commit() self.session.close() sc_db = self.session.query(SomeClass).get(1) assert sc_db.others[0].s == 'ehe1' assert sc_db.others[1].s == 'ehe2' self.session.close() def test_modifiers(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} i = XmlAttribute(Integer32(pk=True)) s = XmlData(Unicode(64)) self.metadata.create_all() self.session.add(SomeClass(s='s')) self.session.commit() self.session.expunge_all() ret = self.session.query(SomeClass).get(1) assert ret.i == 1 # redundant assert ret.s == 's' def test_default_ctor(self): class SomeOtherClass(ComplexModel): id = Integer32 s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) others = Array(SomeOtherClass, store_as='json') f = Unicode(32, default='uuu') self.metadata.create_all() self.session.add(SomeClass()) self.session.commit() self.session.expunge_all() assert self.session.query(SomeClass).get(1).f == 'uuu' def test_default_value(self): class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) f = Unicode(32, db_default=u'uuu') self.metadata.create_all() val = SomeClass() assert val.f is None self.session.add(val) self.session.commit() self.session.expunge_all() assert self.session.query(SomeClass).get(1).f == u'uuu' def test_default_ctor_with_sql_relationship(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table') self.metadata.create_all() self.session.add(SomeClass()) self.session.commit() def test_store_as_index(self): class SomeOtherClass(TableModel): __tablename__ = 'some_other_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) s = Unicode(64) class SomeClass(TableModel): __tablename__ = 'some_class' __table_args__ = {"sqlite_autoincrement": True} id = Integer32(primary_key=True) o = SomeOtherClass.customize(store_as='table', index='btree') self.metadata.create_all() idx, = SomeClass.__table__.indexes assert 'o_id' in idx.columns def test_scalar_collection(self): class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) values = Array(Unicode).store_as('table') self.metadata.create_all() self.session.add(SomeClass(id=1, values=['a', 'b', 'c'])) self.session.commit() sc = self.session.query(SomeClass).get(1) assert sc.values == ['a', 'b', 'c'] del sc sc = self.session.query(SomeClass).get(1) sc.values.append('d') self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert sc.values == ['a', 'b', 'c', 'd'] sc = self.session.query(SomeClass).get(1) sc.values = sc.values[1:] self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert sc.values == ['b', 'c', 'd'] def test_multiple_fk(self): class SomeChildClass(TableModel): __tablename__ = 'some_child_class' id = Integer32(primary_key=True) s = Unicode(64) i = Integer32 class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) children = Array(SomeChildClass).store_as('table') mirror = SomeChildClass.store_as('table') self.metadata.create_all() children = [ SomeChildClass(s='p', i=600), SomeChildClass(s='|', i=10), SomeChildClass(s='q', i=9), ] sc = SomeClass(children=children) self.session.add(sc) self.session.flush() sc.mirror = children[1] self.session.commit() del sc sc = self.session.query(SomeClass).get(1) assert ''.join([scc.s for scc in sc.children]) == 'p|q' assert sum([scc.i for scc in sc.children]) == 619 def test_simple_fk(self): class SomeChildClass(TableModel): __tablename__ = 'some_child_class' id = Integer32(primary_key=True) s = Unicode(64) i = Integer32 class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) child_id = Integer32(fk='some_child_class.id') foreign_keys = SomeClass.__table__.c['child_id'].foreign_keys assert len(foreign_keys) == 1 fk, = foreign_keys assert fk._colspec == 'some_child_class.id' def test_multirel_single_table(self): class SomeChildClass(TableModel): __tablename__ = 'some_child_class' id = Integer32(primary_key=True) s = Unicode(64) class SomeOtherChildClass(TableModel): __tablename__ = 'some_other_child_class' id = Integer32(primary_key=True) i = Integer32 class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) children = Array(SomeChildClass, store_as=table( multi='children', lazy='joined', left='parent_id', right='child_id', fk_left_ondelete='cascade', fk_right_ondelete='cascade', ), ) other_children = Array(SomeOtherChildClass, store_as=table( multi='children', lazy='joined', left='parent_id', right='other_child_id', fk_left_ondelete='cascade', fk_right_ondelete='cascade', ), ) t = SomeClass.Attributes.sqla_metadata.tables['children'] fkp, = t.c.parent_id.foreign_keys assert fkp._colspec == 'some_class.id' fkc, = t.c.child_id.foreign_keys assert fkc._colspec == 'some_child_class.id' fkoc, = t.c.other_child_id.foreign_keys assert fkoc._colspec == 'some_other_child_class.id' def test_reflection(self): class SomeClass(TableModel): __tablename__ = 'some_class' id = Integer32(primary_key=True) s = Unicode(32) TableModel.Attributes.sqla_metadata.create_all() # create a new table model with empty metadata TM2 = TTableModel() TM2.Attributes.sqla_metadata.bind = self.engine # fill it with information from the db TM2.Attributes.sqla_metadata.reflect() # convert sqla info to spyne info class Reflected(TM2): __table__ = TM2.Attributes.sqla_metadata.tables['some_class'] pprint(dict(Reflected._type_info).items()) assert issubclass(Reflected._type_info['id'], Integer) # this looks at spyne attrs assert [k for k, v in get_pk_columns(Reflected)] == ['id'] # this looks at sqla attrs assert [k for k, v in Reflected.get_primary_keys()] == ['id'] assert issubclass(Reflected._type_info['s'], Unicode) assert Reflected._type_info['s'].Attributes.max_len == 32 def _test_sqlalchemy_remapping(self): class SomeTable(TableModel): __tablename__ = 'some_table' id = Integer32(pk=True) i = Integer32 s = Unicode(32) class SomeTableSubset(TableModel): __table__ = SomeTable.__table__ id = Integer32(pk=True) # sqla session doesn't work without pk i = Integer32 class SomeTableOtherSubset(TableModel): __table__ = SomeTable.__table__ _type_info = [(k, v) for k, v in SomeTable._type_info.items() if k in ('id', 's')] self.session.add(SomeTable(id=1, i=2, s='s')) self.session.commit() st = self.session.query(SomeTable).get(1) sts = self.session.query(SomeTableSubset).get(1) stos = self.session.query(SomeTableOtherSubset).get(1) sts.i = 3 sts.s = 'ss' # will not be flushed to db self.session.commit() assert st.s == 's' assert stos.i == 3 def test_file_storage(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) f = File(store_as=HybridFileStore('test_file_storage', 'json')) self.metadata.create_all() c = C(f=File.Value(name=u"name", type=u"type", data=[b"data"])) self.session.add(c) self.session.flush() self.session.commit() c = self.session.query(C).get(1) print(c) assert c.f.name == "name" assert c.f.type == "type" assert c.f.data[0][:] == b"data" def test_append_field_complex_existing_column(self): class C(TableModel): __tablename__ = "c" u = Unicode(pk=True) class D(TableModel): __tablename__ = "d" d = Integer32(pk=True) c = C.store_as('table') C.append_field('d', D.store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D def test_append_field_complex_delayed(self): class C(TableModel): __tablename__ = "c" u = Unicode(pk=True) class D(C): i = Integer32 C.append_field('d', DateTime) assert D.Attributes.sqla_mapper.has_property('d') def _test_append_field_complex_explicit_existing_column(self): # FIXME: Test something! class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) # c already also produces c_id. this is undefined behaviour, one of them # gets ignored, whichever comes first. class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) c = C.store_as('table') c_id = Integer32(15) def test_append_field_complex_circular_array(self): class C(TableModel): __tablename__ = "cc" id = Integer32(pk=True) class D(TableModel): __tablename__ = "dd" id = Integer32(pk=True) c = Array(C).customize(store_as=table(right='dd_id')) C.append_field('d', D.customize(store_as=table(left='dd_id'))) self.metadata.create_all() c1, c2 = C(id=1), C(id=2) d = D(id=1, c=[c1, c2]) self.session.add(d) self.session.commit() assert c1.d.id == 1 def test_append_field_complex_new_column(self): class C(TableModel): __tablename__ = "c" u = Unicode(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', D.store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D assert isinstance(C.Attributes.sqla_table.c['d_id'].type, sqlalchemy.Integer) def test_append_field_array(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', Array(D).store_as('table')) assert C.Attributes.sqla_mapper.get_property('d').argument is D print(repr(D.Attributes.sqla_table)) assert isinstance(D.Attributes.sqla_table.c['c_id'].type, sqlalchemy.Integer) def test_append_field_array_many(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) C.append_field('d', Array(D).store_as(table(multi='c_d'))) assert C.Attributes.sqla_mapper.get_property('d').argument is D rel_table = C.Attributes.sqla_metadata.tables['c_d'] assert 'c_id' in rel_table.c assert 'd_id' in rel_table.c def test_append_field_complex_cust(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) class D(TableModel): __tablename__ = "d" id = Integer32(pk=True) c = Array(C).store_as('table') C.append_field('d', D.customize( nullable=False, store_as=table(left='d_id'), )) assert C.__table__.c['d_id'].nullable == False def _test_append_field_cust(self): class C(TableModel): __tablename__ = "c" id = Integer32(pk=True) C2 = C.customize() C.append_field("s", Unicode) C() self.metadata.create_all() assert "s" in C2._type_info assert "s" in C2.Attributes.sqla_mapper.columns self.session.add(C2(s='foo')) self.session.commit() assert self.session.query(C).first().s == 'foo' def test_polymorphic_cust(self): class C(TableModel): __tablename__ = "c" __mapper_args__ = { 'polymorphic_on': 't', 'polymorphic_identity': 1, } id = Integer32(pk=True) t = M(Integer32) class D(C): __mapper_args__ = { 'polymorphic_identity': 2, } d = Unicode D2 = D.customize() assert C().t == 1 assert D().t == 2 # That's the way SQLAlchemy works. Don't use customized classes in # anywhere other than interface definitions assert D2().t == None def test_base_append_simple(self): class B(TableModel): __tablename__ = 'b' __mapper_args__ = { 'polymorphic_on': 't', 'polymorphic_identity': 1, } id = Integer32(pk=True) t = M(Integer32) class C(B): __mapper_args__ = { 'polymorphic_identity': 1, } s = Unicode B.append_field('i', Integer32) self.metadata.create_all() self.session.add(C(s="foo", i=42)) self.session.commit() c = self.session.query(C).first() assert c.s == 'foo' assert c.i == 42 assert c.t == 1 def test_base_append_complex(self): class B(TableModel): __tablename__ = 'b' __mapper_args__ = { 'polymorphic_on': 't', 'polymorphic_identity': 1, } id = Integer32(pk=True) t = M(Integer32) class C(B): __mapper_args__ = { 'polymorphic_identity': 1, } s = Unicode class D(TableModel): __tablename__ = 'd' id = Integer32(pk=True) i = M(Integer32) B.append_field('d', D.store_as('table')) self.metadata.create_all() self.session.add(C(d=D(i=42))) self.session.commit() c = self.session.query(C).first() assert c.d.i == 42 class TestSqlAlchemySchemaWithPostgresql(unittest.TestCase): def setUp(self): self.metadata = TableModel.Attributes.sqla_metadata = MetaData() def test_enum(self): table_name = "test_enum" enums = ('SUBSCRIBED', 'UNSUBSCRIBED', 'UNCONFIRMED') class SomeClass(TableModel): __tablename__ = table_name id = Integer32(primary_key=True) e = Enum(*enums, type_name='status_choices') t = self.metadata.tables[table_name] assert 'e' in t.c assert tuple(t.c.e.type.enums) == enums if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/transport/000077500000000000000000000000001417664205300202515ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/transport/__init__.py000066400000000000000000000000001417664205300223500ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/transport/test_msgpack.py000066400000000000000000000061651417664205300233170ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import msgpack from spyne import Application, Service, rpc from spyne.model import Unicode from spyne.protocol.msgpack import MessagePackDocument from twisted.trial import unittest class TestMessagePackServer(unittest.TestCase): def gen_prot(self, app): from spyne.server.twisted.msgpack import TwistedMessagePackProtocol from twisted.test.proto_helpers import StringTransportWithDisconnection from spyne.server.msgpack import MessagePackServerBase prot = TwistedMessagePackProtocol(MessagePackServerBase(app)) transport = StringTransportWithDisconnection() prot.makeConnection(transport) transport.protocol = prot return prot def test_roundtrip(self): v = "yaaay!" class SomeService(Service): @rpc(Unicode, _returns=Unicode) def yay(ctx, u): return u app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) prot = self.gen_prot(app) request = msgpack.packb({'yay': [v]}) prot.dataReceived(msgpack.packb([1, request])) val = prot.transport.value() print(repr(val)) val = msgpack.unpackb(val) print(repr(val)) self.assertEqual(val, [0, msgpack.packb(v)]) def test_roundtrip_deferred(self): from twisted.internet import reactor from twisted.internet.task import deferLater v = "yaaay!" p_ctx = [] class SomeService(Service): @rpc(Unicode, _returns=Unicode) def yay(ctx, u): def _cb(): return u p_ctx.append(ctx) return deferLater(reactor, 0.1, _cb) app = Application([SomeService], 'tns', in_protocol=MessagePackDocument(), out_protocol=MessagePackDocument()) prot = self.gen_prot(app) request = msgpack.packb({'yay': [v]}) def _ccb(_): val = prot.transport.value() print(repr(val)) val = msgpack.unpackb(val) print(repr(val)) self.assertEqual(val, [0, msgpack.packb(v)]) prot.dataReceived(msgpack.packb([1, request])) return p_ctx[0].out_object[0].addCallback(_ccb) spyne-spyne-2.14.0/spyne/test/util/000077500000000000000000000000001417664205300171725ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/util/__init__.py000066400000000000000000000000001417664205300212710ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/test/util/test_address.py000077500000000000000000000567361417664205300222540ustar00rootroot00000000000000#!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # The MIT License # # Copyright (c) Val Neekman @ Neekware Inc. http://neekware.com # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # from unittest import TestCase from spyne.util.address import set_address_parser_settings set_address_parser_settings(trusted_proxies=['177.139.233.100']) from spyne.util.address import address_parser class IPv4TestCase(TestCase): """IP address Test""" def test_meta_none(self): request = { } ip = address_parser.get_real_ip(request) self.assertIsNone(ip) def test_http_x_forwarded_for_multiple(self): request = { 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_multiple_left_most_ip(self): request = { 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_multiple_right_most_ip(self): request = { 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request, right_most_proxy=True) self.assertEqual(ip, "177.139.233.139") def test_http_x_forwarded_for_multiple_right_most_ip_private(self): request = { 'HTTP_X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request, right_most_proxy=True) self.assertEqual(ip, "177.139.233.139") def test_http_x_forwarded_for_multiple_bad_address(self): request = { 'HTTP_X_FORWARDED_FOR': 'unknown, 192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_singleton(self): request = { 'HTTP_X_FORWARDED_FOR': '177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.139") def test_http_x_forwarded_for_singleton_private_address(self): request = { 'HTTP_X_FORWARDED_FOR': '192.168.255.182', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.132") def test_bad_http_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'HTTP_X_FORWARDED_FOR': 'unknown 177.139.233.139', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.132") def test_empty_http_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '177.139.233.132', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.132") def test_empty_http_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_empty_http_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '192.168.255.182', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_private_http_x_forward_for_ip_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '127.0.0.1', 'HTTP_X_REAL_IP': '', 'REMOTE_ADDR': '', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_private_remote_addr_for_ip_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'REMOTE_ADDR': '127.0.0.1', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_missing_x_forwarded(self): request = { 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_missing_x_forwarded_missing_real_ip(self): request = { 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_best_matched_real_ip(self): request = { 'HTTP_X_REAL_IP': '127.0.0.1', 'REMOTE_ADDR': '172.31.233.133', } ip = address_parser.get_ip(request) self.assertEqual(ip, "172.31.233.133") def test_best_matched_private_ip(self): request = { 'HTTP_X_REAL_IP': '127.0.0.1', 'REMOTE_ADDR': '192.31.233.133', } ip = address_parser.get_ip(request) self.assertEqual(ip, "192.31.233.133") def test_best_matched_private_ip_2(self): request = { 'HTTP_X_REAL_IP': '192.31.233.133', 'REMOTE_ADDR': '127.0.0.1', } ip = address_parser.get_ip(request) self.assertEqual(ip, "192.31.233.133") def test_x_forwarded_for_multiple(self): request = { 'X_FORWARDED_FOR': '192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_x_forwarded_for_multiple_left_most_ip(self): request = { 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_x_forwarded_for_multiple_right_most_ip(self): request = { 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request, right_most_proxy=True) self.assertEqual(ip, "177.139.233.139") def test_x_forwarded_for_multiple_right_most_ip_private(self): request = { 'X_FORWARDED_FOR': '192.168.255.182, 198.84.193.157, 10.0.0.0, 127.0.0.1, 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request, right_most_proxy=True) self.assertEqual(ip, "177.139.233.139") def test_x_forwarded_for_multiple_bad_address(self): request = { 'X_FORWARDED_FOR': 'unknown, 192.168.255.182, 10.0.0.0, 127.0.0.1, 198.84.193.157, 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "198.84.193.157") def test_x_forwarded_for_singleton(self): request = { 'X_FORWARDED_FOR': '177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.139") def test_x_forwarded_for_singleton_private_address(self): request = { 'X_FORWARDED_FOR': '192.168.255.182', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_bad_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'X_FORWARDED_FOR': 'unknown 177.139.233.139', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_empty_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_empty_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_empty_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.133") def test_private_x_forward_for_ip_addr(self): request = { 'X_FORWARDED_FOR': '127.0.0.1', 'REMOTE_ADDR': '', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_x_forwarded_for_singleton_hyphen_as_delimiter(self): request = { 'X-FORWARDED-FOR': '177.139.233.139', 'REMOTE-ADDR': '177.139.233.133', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "177.139.233.139") class IPv4TrustedProxiesTestCase(TestCase): """Trusted Proxies - IP address Test""" def test_meta_none(self): request = { } ip = address_parser.get_trusted_ip(request) self.assertIsNone(ip) def test_http_x_forwarded_for_conf_settings(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.100', } ip = address_parser.get_trusted_ip(request) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_no_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=[]) self.assertIsNone(ip) def test_http_x_forwarded_for_single_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_single_proxy_with_right_most(self): request = { 'HTTP_X_FORWARDED_FOR': '177.139.233.139, 177.139.200.139, 198.84.193.157', } ip = address_parser.get_trusted_ip(request, right_most_proxy=True, trusted_proxies=['177.139.233.139']) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_multi_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.138', '177.139.233.139']) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_all_proxies_in_subnet(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233']) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_all_proxies_in_subnet_2(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139']) self.assertEqual(ip, "198.84.193.157") def test_x_forwarded_for_single_proxy(self): request = { 'X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) self.assertEqual(ip, "198.84.193.157") def test_x_forwarded_for_single_proxy_hyphens(self): request = { 'X-FORWARDED-FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) self.assertEqual(ip, "198.84.193.157") def test_http_x_forwarded_for_and_x_forward_for_single_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '198.84.193.156, 177.139.200.139, 177.139.233.139', 'X_FORWARDED_FOR': '198.84.193.157, 177.139.200.139, 177.139.233.139', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['177.139.233.139']) self.assertEqual(ip, "198.84.193.156") class IPv6TestCase(TestCase): """IP address Test""" def test_http_x_forwarded_for_multiple(self): request = { 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") def test_http_x_forwarded_for_multiple_bad_address(self): request = { 'HTTP_X_FORWARDED_FOR': 'unknown, ::1/128, 74dc::02ba', 'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_http_x_forwarded_for_singleton(self): request = { 'HTTP_X_FORWARDED_FOR': '74dc::02ba', 'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_http_x_forwarded_for_singleton_private_address(self): request = { 'HTTP_X_FORWARDED_FOR': '::1/128', 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_bad_http_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'HTTP_X_FORWARDED_FOR': 'unknown ::1/128', 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_empty_http_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_empty_http_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_empty_http_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '::1/128', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_private_http_x_forward_for_ip_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '::1/128', 'HTTP_X_REAL_IP': '', 'REMOTE_ADDR': '', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_private_real_ip_for_ip_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '::1/128', 'REMOTE_ADDR': '', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_private_remote_addr_for_ip_addr(self): request = { 'HTTP_X_FORWARDED_FOR': '', 'HTTP_X_REAL_IP': '', 'REMOTE_ADDR': '::1/128', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_missing_x_forwarded(self): request = { 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_missing_x_forwarded_missing_real_ip(self): request = { 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_missing_x_forwarded_missing_real_ip_mix_case(self): request = { 'REMOTE_ADDR': '74DC::02BA', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_private_remote_address(self): request = { 'REMOTE_ADDR': 'fe80::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_best_matched_real_ip(self): request = { 'HTTP_X_REAL_IP': '::1', 'REMOTE_ADDR': 'fe80::02ba', } ip = address_parser.get_ip(request) self.assertEqual(ip, "fe80::02ba") def test_x_forwarded_for_multiple(self): request = { 'X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") def test_x_forwarded_for_multiple_bad_address(self): request = { 'X_FORWARDED_FOR': 'unknown, ::1/128, 74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_x_forwarded_for_singleton(self): request = { 'X_FORWARDED_FOR': '74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_x_forwarded_for_singleton_private_address(self): request = { 'X_FORWARDED_FOR': '::1/128', 'HTTP_X_REAL_IP': '74dc::02ba', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_bad_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'X_FORWARDED_FOR': 'unknown ::1/128', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") def test_empty_x_forwarded_for_fallback_on_x_real_ip(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") def test_empty_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_empty_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self): request = { 'X_FORWARDED_FOR': '', 'REMOTE_ADDR': '74dc::02ba', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") def test_private_x_forward_for_ip_addr(self): request = { 'X_FORWARDED_FOR': '::1/128', 'REMOTE_ADDR': '', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, None) def test_x_forwarded_for_singleton_hyphen_as_delimiter(self): request = { 'X-FORWARDED-FOR': '74dc::02ba', 'REMOTE-ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf', } ip = address_parser.get_real_ip(request) self.assertEqual(ip, "74dc::02ba") class IPv6TrustedProxiesTestCase(TestCase): """Trusted Proxies - IP address Test""" def test_http_x_forwarded_for_no_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', } ip = address_parser.get_trusted_ip(request, trusted_proxies=[]) self.assertIsNone(ip) def test_http_x_forwarded_for_single_proxy(self): request = { 'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba', } ip = address_parser.get_trusted_ip(request, trusted_proxies=['74dc::02ba']) self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf") spyne-spyne-2.14.0/spyne/test/util/test_util.py000077500000000000000000000377731417664205300216040ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import json import decimal import unittest import pytz import sqlalchemy from pprint import pprint from decimal import Decimal as D from datetime import datetime from lxml import etree from spyne.const import MAX_STRING_FIELD_LENGTH from spyne.decorator import srpc from spyne.application import Application from spyne.model.complex import XmlAttribute, TypeInfo from spyne.model.complex import ComplexModel from spyne.model.complex import Iterable from spyne.model.complex import Array from spyne.model.primitive import Decimal from spyne.model.primitive import DateTime from spyne.model.primitive import Integer from spyne.model.primitive import Unicode from spyne.service import Service from spyne.util import AttrDict, AttrDictColl, get_version from spyne.util import memoize, memoize_ignore_none, memoize_ignore, memoize_id from spyne.util.protocol import deserialize_request_string from spyne.util.dictdoc import get_dict_as_object, get_object_as_yaml, \ get_object_as_json from spyne.util.dictdoc import get_object_as_dict from spyne.util.tdict import tdict from spyne.util.tlist import tlist from spyne.util.xml import get_object_as_xml from spyne.util.xml import get_xml_as_object from spyne.util.xml import get_schema_documents from spyne.util.xml import get_validation_schema class TestUtil(unittest.TestCase): def test_version(self): assert get_version('sqlalchemy') == get_version(sqlalchemy) assert '.'.join([str(i) for i in get_version('sqlalchemy')]) == \ sqlalchemy.__version__ class TestTypeInfo(unittest.TestCase): def test_insert(self): d = TypeInfo() d['a'] = 1 assert d[0] == d['a'] == 1 d.insert(0, ('b', 2)) assert d[1] == d['a'] == 1 assert d[0] == d['b'] == 2 def test_insert_existing(self): d = TypeInfo() d["a"] = 1 d["b"] = 2 assert d[1] == d['b'] == 2 d.insert(0, ('b', 3)) assert d[1] == d['a'] == 1 assert d[0] == d['b'] == 3 def test_update(self): d = TypeInfo() d["a"] = 1 d.update([('b', 2)]) assert d[0] == d['a'] == 1 assert d[1] == d['b'] == 2 class TestXml(unittest.TestCase): def test_serialize(self): class C(ComplexModel): __namespace__ = "tns" i = Integer s = Unicode c = C(i=5, s="x") ret = get_object_as_xml(c, C) print(etree.tostring(ret)) assert ret.tag == "{tns}C" ret = get_object_as_xml(c, C, "X") print(etree.tostring(ret)) assert ret.tag == "{tns}X" ret = get_object_as_xml(c, C, "X", no_namespace=True) print(etree.tostring(ret)) assert ret.tag == "X" ret = get_object_as_xml(c, C, no_namespace=True) print(etree.tostring(ret)) assert ret.tag == "C" def test_deserialize(self): class Punk(ComplexModel): __namespace__ = 'some_namespace' a = Unicode b = Integer c = Decimal d = DateTime class Foo(ComplexModel): __namespace__ = 'some_other_namespace' a = Unicode b = Integer c = Decimal d = DateTime e = XmlAttribute(Integer) def __eq__(self, other): # remember that this is a test object assert ( self.a == other.a and self.b == other.b and self.c == other.c and self.d == other.d and self.e == other.e ) return True docs = get_schema_documents([Punk, Foo]) pprint(docs) assert docs['s0'].tag == '{http://www.w3.org/2001/XMLSchema}schema' assert docs['tns'].tag == '{http://www.w3.org/2001/XMLSchema}schema' print() print("the other namespace %r:" % docs['tns'].attrib['targetNamespace']) assert docs['tns'].attrib['targetNamespace'] == 'some_namespace' print(etree.tostring(docs['tns'], pretty_print=True)) print() print("the other namespace %r:" % docs['s0'].attrib['targetNamespace']) assert docs['s0'].attrib['targetNamespace'] == 'some_other_namespace' print(etree.tostring(docs['s0'], pretty_print=True)) print() foo = Foo(a=u'a', b=1, c=decimal.Decimal('3.4'), d=datetime(2011,2,20,tzinfo=pytz.utc), e=5) doc = get_object_as_xml(foo, Foo) print(etree.tostring(doc, pretty_print=True)) foo_back = get_xml_as_object(doc, Foo) assert foo_back == foo # as long as it doesn't fail, it's ok. get_validation_schema([Punk, Foo]) class TestCDict(unittest.TestCase): def test_cdict(self): from spyne.util.cdict import cdict class A(object): pass class B(A): pass class E(B): pass class F(E): pass class C(object): pass d = cdict({A: "fun", F: 'zan'}) assert d[A] == 'fun' assert d[B] == 'fun' assert d[F] == 'zan' try: d[C] except KeyError: pass else: raise Exception("Must fail.") class TestTDict(unittest.TestCase): def test_tdict_notype(self): d = tdict() d[0] = 1 assert d[0] == 1 d = tdict() d.update({0:1}) assert d[0] == 1 d = tdict.fromkeys([0], 1) assert d[0] == 1 def test_tdict_k(self): d = tdict(str) try: d[0] = 1 except TypeError: pass else: raise Exception("must fail") d = tdict(str) d['s'] = 1 assert d['s'] == 1 def test_tdict_v(self): d = tdict(vt=str) try: d[0] = 1 except TypeError: pass else: raise Exception("must fail") d = tdict(vt=str) d[0] = 's' assert d[0] == 's' class TestLogRepr(unittest.TestCase): def test_log_repr_simple(self): from spyne.model.complex import ComplexModel from spyne.model.primitive import String from spyne.util.web import log_repr class Z(ComplexModel): z=String l = MAX_STRING_FIELD_LENGTH + 100 print(log_repr(Z(z="a" * l))) print("Z(z='%s'(...))" % ('a' * MAX_STRING_FIELD_LENGTH)) assert log_repr(Z(z="a" * l)) == "Z(z='%s'(...))" % \ ('a' * MAX_STRING_FIELD_LENGTH) assert log_repr(['a','b','c'], Array(String)) == "['a', 'b', (...)]" def test_log_repr_complex(self): from spyne.model import ByteArray from spyne.model import File from spyne.model.complex import ComplexModel from spyne.model.primitive import String from spyne.util.web import log_repr class Z(ComplexModel): _type_info = [ ('f', File(logged=False)), ('t', ByteArray(logged=False)), ('z', Array(String)), ] l = MAX_STRING_FIELD_LENGTH + 100 val = Z(z=["abc"] * l, t=['t'], f=File.Value(name='aaa', data=['t'])) print(repr(val)) assert log_repr(val) == "Z(z=['abc', 'abc', (...)])" def test_log_repr_dict_vanilla(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict assert log_repr({1: 1}, t) == "{1: 1}" assert log_repr({1: 1, 2: 2}, t) == "{1: 1, 2: 2}" assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: 1, 2: 2, (...)}" assert log_repr([1], t) == "[1]" assert log_repr([1, 2], t) == "[1, 2]" assert log_repr([1, 2, 3], t) == "[1, 2, (...)]" def test_log_repr_dict_keys(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict(logged='keys') assert log_repr({1: 1}, t) == "{1: (...)}" assert log_repr([1], t) == "[1]" def test_log_repr_dict_values(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict(logged='values') assert log_repr({1: 1}, t) == "{(...): 1}" assert log_repr([1], t) == "[1]" def test_log_repr_dict_full(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict(logged='full') assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: 1, 2: 2, 3: 3}" assert log_repr([1, 2, 3], t) == "[1, 2, 3]" def test_log_repr_dict_keys_full(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict(logged='keys-full') assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{1: (...), 2: (...), 3: (...)}" assert log_repr([1, 2, 3], t) == "[1, 2, 3]" def test_log_repr_dict_values_full(self): from spyne.model import AnyDict from spyne.util.web import log_repr t = AnyDict(logged='values-full') assert log_repr({1: 1, 2: 2, 3: 3}, t) == "{(...): 1, (...): 2, (...): 3}" assert log_repr([1, 2, 3], t) == "[1, 2, 3]" class TestDeserialize(unittest.TestCase): def test_deserialize(self): from spyne.protocol.soap import Soap11 class SomeService(Service): @srpc(Integer, _returns=Iterable(Integer)) def some_call(yo): return range(yo) app = Application([SomeService], 'tns', in_protocol=Soap11(), out_protocol=Soap11()) meat = 30 string = """ %s """ % meat obj = deserialize_request_string(string, app) assert obj.yo == meat class TestEtreeDict(unittest.TestCase): longMessage = True def test_simple(self): from lxml.etree import tostring from spyne.util.etreeconv import root_dict_to_etree assert tostring(root_dict_to_etree({'a':{'b':'c'}})) == b'c' def test_not_sized(self): from lxml.etree import tostring from spyne.util.etreeconv import root_dict_to_etree complex_value = root_dict_to_etree({'a':{'b':1}}) self.assertEqual(tostring(complex_value), b'1', "The integer should be properly rendered in the etree") complex_none = root_dict_to_etree({'a':{'b':None}}) self.assertEqual(tostring(complex_none), b'', "None should not be rendered in the etree") simple_value = root_dict_to_etree({'a': 1}) self.assertEqual(tostring(simple_value), b'1', "The integer should be properly rendered in the etree") none_value = root_dict_to_etree({'a': None}) self.assertEqual(tostring(none_value), b'', "None should not be rendered in the etree") string_value = root_dict_to_etree({'a': 'lol'}) self.assertEqual(tostring(string_value), b'lol', "A string should be rendered as a string") complex_string_value = root_dict_to_etree({'a': {'b': 'lol'}}) self.assertEqual(tostring(complex_string_value), b'lol', "A string should be rendered as a string") class TestDictDoc(unittest.TestCase): def test_the(self): class C(ComplexModel): __namespace__ = "tns" i = Integer s = Unicode a = Array(DateTime) def __eq__(self, other): print("Yaaay!") return self.i == other.i and \ self.s == other.s and \ self.a == other.a c = C(i=5, s="x", a=[datetime(2011,12,22, tzinfo=pytz.utc)]) for iw, ca in ((False,dict), (True,dict), (False,list), (True, list)): print() print('complex_as:', ca) d = get_object_as_dict(c, C, complex_as=ca) print(d) o = get_dict_as_object(d, C, complex_as=ca) print(o) print(c) assert o == c class TestAttrDict(unittest.TestCase): def test_attr_dict(self): assert AttrDict(a=1)['a'] == 1 def test_attr_dict_coll(self): assert AttrDictColl('SomeDict').SomeDict.NAME == 'SomeDict' assert AttrDictColl('SomeDict').SomeDict(a=1)['a'] == 1 assert AttrDictColl('SomeDict').SomeDict(a=1).NAME == 'SomeDict' class TestYaml(unittest.TestCase): def test_deser(self): class C(ComplexModel): a = Unicode b = Decimal ret = get_object_as_yaml(C(a='burak', b=D(30)), C) assert ret == b"""C: a: burak b: '30' """ class TestJson(unittest.TestCase): def test_deser(self): class C(ComplexModel): _type_info = [ ('a', Unicode), ('b', Decimal), ] ret = get_object_as_json(C(a='burak', b=D(30)), C) assert ret == b'["burak", "30"]' ret = get_object_as_json(C(a='burak', b=D(30)), C, complex_as=dict) assert json.loads(ret.decode('utf8')) == \ json.loads(u'{"a": "burak", "b": "30"}') class TestFifo(unittest.TestCase): def test_msgpack_fifo(self): import msgpack v1 = [1, 2, 3, 4] v2 = [5, 6, 7, 8] v3 = {b"a": 9, b"b": 10, b"c": 11} s1 = msgpack.packb(v1) s2 = msgpack.packb(v2) s3 = msgpack.packb(v3) unpacker = msgpack.Unpacker() unpacker.feed(s1) unpacker.feed(s2) unpacker.feed(s3[:4]) assert next(iter(unpacker)) == v1 assert next(iter(unpacker)) == v2 try: next(iter(unpacker)) except StopIteration: pass else: raise Exception("must fail") unpacker.feed(s3[4:]) assert next(iter(unpacker)) == v3 class TestTlist(unittest.TestCase): def test_tlist(self): tlist([], int) a = tlist([1, 2], int) a.append(3) a += [4] a = [5] + [a] a = a + [6] a[0] = 1 a[5:] = [5] try: tlist([1, 2, 'a'], int) a.append('a') a += ['a'] _ = ['a'] + a _ = a + ['a'] a[0] = 'a' a[0:] = 'a' except TypeError: pass else: raise Exception("Must fail") class TestMemoization(unittest.TestCase): def test_memoize(self): counter = [0] @memoize def f(arg): counter[0] += 1 print(arg, counter) f(1) f(1) assert counter[0] == 1 f(2) assert counter[0] == 2 def test_memoize_ignore_none(self): counter = [0] @memoize_ignore_none def f(arg): counter[0] += 1 print(arg, counter) return arg f(None) f(None) assert counter[0] == 2 f(1) assert counter[0] == 3 f(1) assert counter[0] == 3 def test_memoize_ignore_values(self): counter = [0] @memoize_ignore((1,)) def f(arg): counter[0] += 1 print(arg, counter) return arg f(1) f(1) assert counter[0] == 2 f(2) assert counter[0] == 3 f(2) assert counter[0] == 3 def test_memoize_id(self): counter = [0] @memoize_id def f(arg): counter[0] += 1 print(arg, counter) return arg d = {} f(d) f(d) assert counter[0] == 1 f({}) assert counter[0] == 2 f({}) assert counter[0] == 3 if __name__ == '__main__': unittest.main() spyne-spyne-2.14.0/spyne/test/wsdl.xml000066400000000000000000002636431417664205300177260ustar00rootroot00000000000000 spyne-spyne-2.14.0/spyne/util/000077500000000000000000000000001417664205300162135ustar00rootroot00000000000000spyne-spyne-2.14.0/spyne/util/__init__.py000066400000000000000000000060441417664205300203300ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import logging logger = logging.getLogger(__name__) from spyne.util import six from spyne.util.coopmt import keepfirst from spyne.util.coopmt import coroutine from spyne.util.coopmt import Break from spyne.util.memo import memoize from spyne.util.memo import memoize_first from spyne.util.memo import memoize_ignore from spyne.util.memo import memoize_ignore_none from spyne.util.memo import memoize_id from spyne.util.attrdict import AttrDict from spyne.util.attrdict import AttrDictColl from spyne.util.attrdict import DefaultAttrDict from spyne.util._base import utctime from spyne.util._base import get_version try: import thread from urllib import splittype, splithost, quote, urlencode from urllib2 import urlopen, Request, HTTPError except ImportError: # Python 3 import _thread as thread from urllib.parse import splittype, splithost, quote, urlencode from urllib.request import urlopen, Request from urllib.error import HTTPError def split_url(url): """Splits a url into (uri_scheme, host[:port], path)""" scheme, remainder = splittype(url) host, path = splithost(remainder) return scheme.lower(), host, path def sanitize_args(a): try: args, kwargs = a if isinstance(args, tuple) and isinstance(kwargs, dict): return args, dict(kwargs) except (TypeError, ValueError): args, kwargs = (), {} if a is not None: if isinstance(a, dict): args = tuple() kwargs = a elif isinstance(a, tuple): if isinstance(a[-1], dict): args, kwargs = a[0:-1], a[-1] else: args = a kwargs = {} return args, kwargs if six.PY2: def _bytes_join(val, joiner=''): return joiner.join(val) else: def _bytes_join(val, joiner=b''): if isinstance(val, six.binary_type): return val return joiner.join(val) def utf8(s): if isinstance(s, bytes): return s.decode('utf8') if isinstance(s, list): return [utf8(ss) for ss in s] if isinstance(s, tuple): return tuple([utf8(ss) for ss in s]) if isinstance(s, set): return {utf8(ss) for ss in s} if isinstance(s, frozenset): return frozenset([utf8(ss) for ss in s]) return s spyne-spyne-2.14.0/spyne/util/_base.py000066400000000000000000000024151417664205300176400ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from time import mktime from datetime import datetime from spyne.util import memoize, six def utctime(): return mktime(datetime.utcnow().timetuple()) @memoize def get_version(package): if isinstance(package, (six.text_type, six.binary_type)): package = __import__(package) verstr = getattr(package, '__version__') retval = [] for f in verstr.split("."): try: retval.append(int(f)) except ValueError: retval.append(f) return tuple(retval) spyne-spyne-2.14.0/spyne/util/_twisted_ws.py000066400000000000000000000440371417664205300211300ustar00rootroot00000000000000# -*- test-case-name: twisted.web.test.test_websockets -*- # Copyright (c) Twisted Matrix Laboratories. # 2011-2012 Oregon State University Open Source Lab # 2011-2012 Corbin Simpson # # See LICENSE for details. """ The WebSockets protocol (RFC 6455), provided as a resource which wraps a factory. """ __all__ = ["WebSocketsResource", "IWebSocketsProtocol", "IWebSocketsResource", "WebSocketsProtocol", "WebSocketsProtocolWrapper"] from hashlib import sha1 from struct import pack, unpack from zope.interface import implementer, Interface, providedBy, directlyProvides from twisted.python import log from twisted.python.constants import Flags, FlagConstant from twisted.internet.protocol import Protocol from twisted.internet.interfaces import IProtocol from twisted.web.resource import IResource from twisted.web.server import NOT_DONE_YET class _WSException(Exception): """ Internal exception for control flow inside the WebSockets frame parser. """ class CONTROLS(Flags): """ Control frame specifiers. """ CONTINUE = FlagConstant(0) TEXT = FlagConstant(1) BINARY = FlagConstant(2) CLOSE = FlagConstant(8) PING = FlagConstant(9) PONG = FlagConstant(10) # The GUID for WebSockets, from RFC 6455. _WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" def _makeAccept(key): """ Create an B{accept} response for a given key. @type key: C{str} @param key: The key to respond to. @rtype: C{str} @return: An encoded response. """ return sha1("%s%s" % (key, _WS_GUID)).digest().encode("base64").strip() def _mask(buf, key): """ Mask or unmask a buffer of bytes with a masking key. @type buf: C{str} @param buf: A buffer of bytes. @type key: C{str} @param key: The masking key. Must be exactly four bytes. @rtype: C{str} @return: A masked buffer of bytes. """ key = [ord(i) for i in key] buf = list(buf) for i, char in enumerate(buf): buf[i] = chr(ord(char) ^ key[i % 4]) return "".join(buf) def _makeFrame(buf, opcode, fin, mask=None): """ Make a frame. This function always creates unmasked frames, and attempts to use the smallest possible lengths. @type buf: C{str} @param buf: A buffer of bytes. @type opcode: C{CONTROLS} @param opcode: Which type of frame to create. @rtype: C{str} @return: A packed frame. """ bufferLength = len(buf) if mask is not None: lengthMask = 0x80 else: lengthMask = 0 if bufferLength > 0xffff: length = "%s%s" % (chr(lengthMask | 0x7f), pack(">Q", bufferLength)) elif bufferLength > 0x7d: length = "%s%s" % (chr(lengthMask | 0x7e), pack(">H", bufferLength)) else: length = chr(lengthMask | bufferLength) if fin: header = 0x80 else: header = 0x01 header = chr(header | opcode.value) if mask is not None: buf = "%s%s" % (mask, _mask(buf, mask)) frame = "%s%s%s" % (header, length, buf) return frame def _parseFrames(frameBuffer, needMask=True): """ Parse frames in a highly compliant manner. @param frameBuffer: A buffer of bytes. @type frameBuffer: C{list} @param needMask: If C{True}, refuse any frame which is not masked. @type needMask: C{bool} """ start = 0 payload = "".join(frameBuffer) while True: # If there's not at least two bytes in the buffer, bail. if len(payload) - start < 2: break # Grab the header. This single byte holds some flags and an opcode header = ord(payload[start]) if header & 0x70: # At least one of the reserved flags is set. Pork chop sandwiches! raise _WSException("Reserved flag in frame (%d)" % header) fin = header & 0x80 # Get the opcode, and translate it to a local enum which we actually # care about. opcode = header & 0xf try: opcode = CONTROLS.lookupByValue(opcode) except ValueError: raise _WSException("Unknown opcode %d in frame" % opcode) # Get the payload length and determine whether we need to look for an # extra length. length = ord(payload[start + 1]) masked = length & 0x80 if not masked and needMask: # The client must mask the data sent raise _WSException("Received data not masked") length &= 0x7f # The offset we'll be using to walk through the frame. We use this # because the offset is variable depending on the length and mask. offset = 2 # Extra length fields. if length == 0x7e: if len(payload) - start < 4: break length = payload[start + 2:start + 4] length = unpack(">H", length)[0] offset += 2 elif length == 0x7f: if len(payload) - start < 10: break # Protocol bug: The top bit of this long long *must* be cleared; # that is, it is expected to be interpreted as signed. length = payload[start + 2:start + 10] length = unpack(">Q", length)[0] offset += 8 if masked: if len(payload) - (start + offset) < 4: # This is not strictly necessary, but it's more explicit so # that we don't create an invalid key. break key = payload[start + offset:start + offset + 4] offset += 4 if len(payload) - (start + offset) < length: break data = payload[start + offset:start + offset + length] if masked: data = _mask(data, key) if opcode == CONTROLS.CLOSE: if len(data) >= 2: # Gotta unpack the opcode and return usable data here. data = unpack(">H", data[:2])[0], data[2:] else: # No reason given; use generic data. data = 1000, "No reason given" yield opcode, data, bool(fin) start += offset + length if len(payload) > start: frameBuffer[:] = [payload[start:]] else: frameBuffer[:] = [] class IWebSocketsProtocol(IProtocol): """ A protocol which understands the WebSockets interface. @since: 13.1 """ def sendFrame(opcode, data, fin): """ Send a frame. """ def frameReceived(opcode, data, fin): """ Callback when a frame is received. """ def loseConnection(): """ Close the connection sending a close frame first. """ @implementer(IWebSocketsProtocol) class WebSocketsProtocol(Protocol): """ @since: 13.1 """ _disconnecting = False _buffer = None def connectionMade(self): """ Log the new connection and initialize the buffer list. """ log.msg("Opening connection with %s" % self.transport.getPeer()) self._buffer = [] def _parseFrames(self): """ Find frames in incoming data and pass them to the underlying protocol. """ for frame in _parseFrames(self._buffer): opcode, data, fin = frame if opcode in (CONTROLS.CONTINUE, CONTROLS.TEXT, CONTROLS.BINARY): # Business as usual. Decode the frame, if we have a decoder. # Pass the frame to the underlying protocol. self.frameReceived(opcode, data, fin) elif opcode == CONTROLS.CLOSE: # The other side wants us to close. reason, text = data log.msg("Closing connection: %r (%d)" % (text, reason)) # Close the connection. self.transport.loseConnection() return elif opcode == CONTROLS.PING: # 5.5.2 PINGs must be responded to with PONGs. # 5.5.3 PONGs must contain the data that was sent with the # provoking PING. self.transport.write(_makeFrame(data, CONTROLS.PONG, True)) def frameReceived(self, opcode, data, fin): """ Callback to implement. """ raise NotImplementedError() def sendFrame(self, opcode, data, fin): """ Build a frame packet and send it over the wire. """ packet = _makeFrame(data, opcode, fin) self.transport.write(packet) def dataReceived(self, data): """ Append the data to the buffer list and parse the whole. """ self._buffer.append(data) try: self._parseFrames() except _WSException: # Couldn't parse all the frames, something went wrong, let's bail. log.err() self.transport.loseConnection() def loseConnection(self): """ Close the connection. This includes telling the other side we're closing the connection. If the other side didn't signal that the connection is being closed, then we might not see their last message, but since their last message should, according to the spec, be a simple acknowledgement, it shouldn't be a problem. """ # Send a closing frame. It's only polite. (And might keep the browser # from hanging.) if not self._disconnecting: frame = _makeFrame("", CONTROLS.CLOSE, True) self.transport.write(frame) self._disconnecting = True self.transport.loseConnection() class WebSocketsProtocolWrapper(WebSocketsProtocol): """ A protocol wrapper which provides L{IWebSocketsProtocol} by making messages as data frames. @since: 13.1 """ def __init__(self, wrappedProtocol, defaultOpcode=CONTROLS.TEXT): self.wrappedProtocol = wrappedProtocol self.defaultOpcode = defaultOpcode def makeConnection(self, transport): """ Upon connection, provides the transport interface, and forwards ourself as the transport to C{self.wrappedProtocol}. """ directlyProvides(self, providedBy(transport)) WebSocketsProtocol.makeConnection(self, transport) self.wrappedProtocol.makeConnection(self) def connectionMade(self): """ Initialize the list of messages. """ WebSocketsProtocol.connectionMade(self) self._messages = [] def write(self, data): """ Write to the websocket protocol, transforming C{data} in a frame. """ self.sendFrame(self.defaultOpcode, data, True) def writeSequence(self, data): """ Send all chunks from C{data} using C{write}. """ for chunk in data: self.write(chunk) def __getattr__(self, name): """ Forward all non-local attributes and methods to C{self.transport}. """ return getattr(self.transport, name) def frameReceived(self, opcode, data, fin): """ FOr each frame received, accumulate the data (ignoring the opcode), and forwarding the messages if C{fin} is set. """ self._messages.append(data) if fin: content = "".join(self._messages) self._messages[:] = [] self.wrappedProtocol.dataReceived(content) def connectionLost(self, reason): """ Forward C{connectionLost} to C{self.wrappedProtocol}. """ self.wrappedProtocol.connectionLost(reason) class IWebSocketsResource(Interface): """ A WebSockets resource. @since: 13.1 """ def lookupProtocol(protocolNames, request): """ Build a protocol instance for the given protocol options and request. The returned protocol is plugged to the HTTP transport, and the returned protocol name, if specified, is used as I{Sec-WebSocket-Protocol} value. If the protocol provides L{IWebSocketsProtocol}, it will be connected directly, otherwise it will be wrapped by L{WebSocketsProtocolWrapper}. @param protocolNames: The asked protocols from the client. @type protocolNames: C{list} of C{str} @param request: The connecting client request. @type request: L{IRequest} @return: A tuple of (protocol, matched protocol name or C{None}). @rtype: C{tuple} """ @implementer(IResource, IWebSocketsResource) class WebSocketsResource(object): """ A resource for serving a protocol through WebSockets. This class wraps a factory and connects it to WebSockets clients. Each connecting client will be connected to a new protocol of the factory. Due to unresolved questions of logistics, this resource cannot have children. @param factory: The factory producing either L{IWebSocketsProtocol} or L{IProtocol} providers, which will be used by the default C{lookupProtocol} implementation. @type factory: L{twisted.internet.protocol.Factory} @since: 13.1 """ isLeaf = True def __init__(self, factory): self._factory = factory def getChildWithDefault(self, name, request): """ Reject attempts to retrieve a child resource. All path segments beyond the one which refers to this resource are handled by the WebSocket connection. """ raise RuntimeError( "Cannot get IResource children from WebSocketsResource") def putChild(self, path, child): """ Reject attempts to add a child resource to this resource. The WebSocket connection handles all path segments beneath this resource, so L{IResource} children can never be found. """ raise RuntimeError( "Cannot put IResource children under WebSocketsResource") def lookupProtocol(self, protocolNames, request): """ Build a protocol instance for the given protocol names and request. This default implementation ignores the protocol names and just return a protocol instance built by C{self._factory}. @param protocolNames: The asked protocols from the client. @type protocolNames: C{list} of C{str} @param request: The connecting client request. @type request: L{Request} @return: A tuple of (protocol, C{None}). @rtype: C{tuple} """ protocol = self._factory.buildProtocol(request.transport.getPeer()) return protocol, None def render(self, request): """ Render a request. We're not actually rendering a request. We are secretly going to handle a WebSockets connection instead. @param request: The connecting client request. @type request: L{Request} @return: a string if the request fails, otherwise C{NOT_DONE_YET}. """ request.defaultContentType = None # If we fail at all, we'll fail with 400 and no response. failed = False if request.method != "GET": # 4.2.1.1 GET is required. failed = True print('request.method', request.method) upgrade = request.getHeader("Upgrade") if upgrade is None or "websocket" not in upgrade.lower(): # 4.2.1.3 Upgrade: WebSocket is required. failed = True print('request.getHeader("Upgrade")', request.getHeader("Upgrade")) connection = request.getHeader("Connection") if connection is None or "upgrade" not in connection.lower(): # 4.2.1.4 Connection: Upgrade is required. failed = True print('request.getHeader("Connection")', request.getHeader("Connection")) key = request.getHeader("Sec-WebSocket-Key") if key is None: # 4.2.1.5 The challenge key is required. failed = True print('request.getHeader("Sec-WebSocket-Key")', request.getHeader("Sec-WebSocket-Key")) version = request.getHeader("Sec-WebSocket-Version") if version != "13": # 4.2.1.6 Only version 13 works. failed = True # 4.4 Forward-compatible version checking. request.setHeader("Sec-WebSocket-Version", "13") print('request.getHeader("Sec-WebSocket-Version")', request.getHeader("Sec-WebSocket-Version")) if failed: request.setResponseCode(400) return "" askedProtocols = request.requestHeaders.getRawHeaders( "Sec-WebSocket-Protocol") protocol, protocolName = self.lookupProtocol(askedProtocols, request) # If a protocol is not created, we deliver an error status. if not protocol: request.setResponseCode(502) return "" # We are going to finish this handshake. We will return a valid status # code. # 4.2.2.5.1 101 Switching Protocols request.setResponseCode(101) # 4.2.2.5.2 Upgrade: websocket request.setHeader("Upgrade", "WebSocket") # 4.2.2.5.3 Connection: Upgrade request.setHeader("Connection", "Upgrade") # 4.2.2.5.4 Response to the key challenge request.setHeader("Sec-WebSocket-Accept", _makeAccept(key)) # 4.2.2.5.5 Optional codec declaration if protocolName: request.setHeader("Sec-WebSocket-Protocol", protocolName) # Provoke request into flushing headers and finishing the handshake. request.write("") # And now take matters into our own hands. We shall manage the # transport's lifecycle. transport, request.transport = request.transport, None if not IWebSocketsProtocol.providedBy(protocol): protocol = WebSocketsProtocolWrapper(protocol) # Connect the transport to our factory, and make things go. We need to # do some stupid stuff here; see #3204, which could fix it. if request.isSecure(): # Secure connections wrap in TLSMemoryBIOProtocol too. transport.protocol.wrappedProtocol = protocol else: transport.protocol = protocol protocol.makeConnection(transport) return NOT_DONE_YET spyne-spyne-2.14.0/spyne/util/address.py000066400000000000000000000217641417664205300202240ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # The MIT License # # Copyright (c) Val Neekman @ Neekware Inc. http://neekware.com # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # from __future__ import print_function # Direct plagiarization of https://github.com/un33k/django-ipware/ # at 57897c03026913892e61a164bc8b022778802ab9 import socket # List of known proxy server(s) TRUSTED_PROXIES = [] # Search for the real IP address in the following order # Configurable via settings.py PRECEDENCE = ( 'HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR', # (client, proxy1, proxy2) OR (proxy2, proxy1, client) 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_VIA', 'REMOTE_ADDR', ) # Private IP addresses # http://en.wikipedia.org/wiki/List_of_assigned_/8_IPv4_address_blocks # http://www.ietf.org/rfc/rfc3330.txt (IPv4) # http://www.ietf.org/rfc/rfc5156.txt (IPv6) # Regex would be ideal here, but this is keeping it simple # as fields are configurable via settings.py PRIVATE_IP_PREFIXES = ( '0.', # externally non-routable '10.', # class A private block '169.254.', # link-local block '172.16.', '172.17.', '172.18.', '172.19.', '172.20.', '172.21.', '172.22.', '172.23.', '172.24.', '172.25.', '172.26.', '172.27.', '172.28.', '172.29.', '172.30.', '172.31.', # class B private blocks '192.0.2.', # reserved for documentation and example code '192.168.', # class C private block '255.255.255.', # IPv4 broadcast address ) + ( '2001:db8:', # reserved for documentation and example code 'fc00:', # IPv6 private block 'fe80:', # link-local unicast 'ff00:', # IPv6 multicast ) LOOPBACK_PREFIX = ( '127.', # IPv4 loopback device '::1', # IPv6 loopback device ) NON_PUBLIC_IP_PREFIXES = PRIVATE_IP_PREFIXES + LOOPBACK_PREFIX def set_address_parser_settings(trusted_proxies, field_precedence=PRECEDENCE, private_ip_prefixes=NON_PUBLIC_IP_PREFIXES): """Changes global parameters for Spyne's residend ip address parser. :param trusted_proxies: Tuple of reverse proxies that are under YOUR control. :param field_precedence: A tuple of field names that may contain address information, in decreasing level of preference. :param private_ip_prefixes: You might want to add your list of public-but-otherwise-private ip prefixes or addresses here. """ global address_parser address_parser = AddressParser(trusted_proxies=trusted_proxies, field_precedence=field_precedence, private_ip_prefixes=private_ip_prefixes) class AddressParser(object): def __init__(self, private_ip_prefixes=None, trusted_proxies=(), field_precedence=PRECEDENCE): if private_ip_prefixes is not None: self.private_ip_prefixes = private_ip_prefixes else: self.private_ip_prefixes = \ tuple([ip.lower() for ip in NON_PUBLIC_IP_PREFIXES]) if len(trusted_proxies) > 0: self.trusted_proxies = trusted_proxies else: self.trusted_proxies = \ tuple([ip.lower() for ip in TRUSTED_PROXIES]) self.field_precedence = field_precedence def get_port(self, wsgi_env): return wsgi_env.get("REMOTE_PORT", 0) def get_ip(self, wsgi_env, real_ip_only=False, right_most_proxy=False): """ Returns client's best-matched ip-address, or None """ best_matched_ip = None for key in self.field_precedence: value = wsgi_env.get(key, None) if value is None: value = wsgi_env.get(key.replace('_', '-'), None) if value is None or value == '': continue ips = [ip.strip().lower() for ip in value.split(',')] if right_most_proxy and len(ips) > 1: ips = reversed(ips) for ip_str in ips: if ip_str is None or ip_str == '' or not \ AddressParser.is_valid_ip(ip_str): continue if not ip_str.startswith(self.private_ip_prefixes): return ip_str if not real_ip_only: loopback = LOOPBACK_PREFIX if best_matched_ip is None: best_matched_ip = ip_str elif best_matched_ip.startswith(loopback) \ and not ip_str.startswith(loopback): best_matched_ip = ip_str return best_matched_ip def get_real_ip(self, wsgi_env, right_most_proxy=False): """ Returns client's best-matched `real` `externally-routable` ip-address, or None """ return self.get_ip(wsgi_env, real_ip_only=True, right_most_proxy=right_most_proxy) def get_trusted_ip(self, wsgi_env, right_most_proxy=False, trusted_proxies=None): """ Returns client's ip-address from `trusted` proxy server(s) or None """ if trusted_proxies is None: trusted_proxies = self.trusted_proxies if trusted_proxies is None or len(trusted_proxies) == 0: trusted_proxies = TRUSTED_PROXIES if trusted_proxies is None or len(trusted_proxies) == 0: return meta_keys = ['HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR'] for key in meta_keys: value = wsgi_env.get(key, None) if value is None: value = wsgi_env.get(key.replace('_', '-'), None) if value is None or value == '': continue ips = [ip.strip().lower() for ip in value.split(',')] if len(ips) > 1: if right_most_proxy: ips.reverse() for proxy in trusted_proxies: if proxy in ips[-1]: return ips[0] @staticmethod def is_valid_ipv4(ip_str): """ Check the validity of an IPv4 address """ if ip_str is None: return False try: socket.inet_pton(socket.AF_INET, ip_str) except AttributeError: # pragma: no cover try: # Fall-back on legacy API or False socket.inet_aton(ip_str) except (AttributeError, socket.error): return False return ip_str.count('.') == 3 except socket.error: return False return True @staticmethod def is_valid_ipv6(ip_str): """ Check the validity of an IPv6 address """ if ip_str is None: return False try: socket.inet_pton(socket.AF_INET6, ip_str) except socket.error: return False return True @staticmethod def is_valid_ip(ip_str): """ Check the validity of an IP address """ return AddressParser.is_valid_ipv4(ip_str) or \ AddressParser.is_valid_ipv6(ip_str) address_parser = AddressParser() spyne-spyne-2.14.0/spyne/util/appreg.py000066400000000000000000000053441417664205300200510ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ Module that contains the Spyne Application Registry. """ import logging logger = logging.getLogger(__name__) applications = {} try: from collections import namedtuple _ApplicationMetaData = namedtuple("_ApplicationMetaData", ['app', 'inst_stack', 'null', 'ostr']) except ImportError: # python 2.5 class _ApplicationMetaData: def __init__(self, app, inst_stack, null, ostr): self.app = app self.inst_stack = inst_stack self.null = null self.ostr = ostr def unregister_application(app): key = (app.tns, app.name) del applications[key] def register_application(app): key = (app.tns, app.name) from spyne.server.null import NullServer try: import traceback stack = traceback.format_stack() except ImportError: stack = None prev = applications.get(key, None) if prev is not None: if hash(prev.app) == hash(app): logger.debug("Application %r previously registered as %r is the same" " as %r. Skipping." % (prev.app, key, app)) prev.inst_stack.append(stack) else: logger.warning("Overwriting application %r(%r)." % (key, app)) if prev.inst_stack is not None: stack_traces = [] for s in prev.inst_stack: if s is not None: stack_traces.append(''.join(s)) logger.debug("Stack trace of the instantiation:\n%s" % '====================\n'.join(stack_traces)) applications[key] = _ApplicationMetaData(app=app, inst_stack=[stack], null=NullServer(app, appinit=False), ostr=NullServer(app, appinit=False, ostr=True) ) logger.debug("Registering %r as %r" % (app, key)) def get_application(tns, name='Application'): return applications.get((tns, name), None) spyne-spyne-2.14.0/spyne/util/attrdict.py000066400000000000000000000055461417664205300204150ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # def TAttrDict(default=None): class AttrDict(object): def __init__(self, *args, **kwargs): self.__data = dict(*args, **kwargs) def __call__(self, **kwargs): retval = AttrDict(self.__data.items()) for k,v in kwargs.items(): setattr(retval, k, v) return retval def __setattr__(self, key, value): if key == "_AttrDict__data": return object.__setattr__(self, key, value) if key == 'items': raise ValueError("'items' is part of dict interface") self.__data[key] = value def __setitem__(self, key, value): self.__data[key] = value def __iter__(self): return iter(self.__data) def items(self): return self.__data.items() def get(self, key, *args): return self.__data.get(key, *args) def update(self, d): return self.__data.update(d) def __repr__(self): return "AttrDict(%s)" % ', '.join(['%s=%r' % (k, v) for k,v in sorted(self.__data.items(), key=lambda x:x[0])]) if default is None: def __getattr__(self, key): return self.__data[key] def __getitem__(self, key): return self.__data[key] else: def __getitem__(self, key): if key in self.__data: return self.__data[key] else: return default() def __getattr__(self, key): if key in ("_AttrDict__data", 'items', 'get', 'update'): return object.__getattribute__(self, '__data') if key in self.__data: return self.__data[key] else: return default() return AttrDict AttrDict = TAttrDict() DefaultAttrDict = TAttrDict(lambda: None) class AttrDictColl(object): AttrDictImpl = DefaultAttrDict def __init__(self, *args): for a in args: setattr(self, a, AttrDictColl.AttrDictImpl(NAME=a)) spyne-spyne-2.14.0/spyne/util/autorel.py000066400000000000000000000233431417664205300202450ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # # Copyright (c) 2004-2016, CherryPy Team (team@cherrypy.org) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the CherryPy Team nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # import logging logger = logging.getLogger(__name__) import os, re, sys from spyne.util.color import YEL # _module__file__base is used by Autoreload to make # absolute any filenames retrieved from sys.modules which are not # already absolute paths. This is to work around Python's quirk # of importing the startup script and using a relative filename # for it in sys.modules. # # Autoreload examines sys.modules afresh every time it runs. If an application # changes the current directory by executing os.chdir(), then the next time # Autoreload runs, it will not be able to find any filenames which are # not absolute paths, because the current directory is not the same as when the # module was first imported. Autoreload will then wrongly conclude the file # has "changed", and initiate the shutdown/re-exec sequence. # See cherrypy ticket #917. # For this workaround to have a decent probability of success, this module # needs to be imported as early as possible, before the app has much chance # to change the working directory. _module__file__base = os.getcwd() try: import fcntl except ImportError: MAX_FILES = 0 else: try: MAX_FILES = os.sysconf('SC_OPEN_MAX') except AttributeError: MAX_FILES = 1024 class AutoReloader(object): """Monitor which re-executes the process when files change. This :ref:`plugin` restarts the process (via :func:`os.execv`) if any of the files it monitors change (or is deleted). By default, the autoreloader monitors all imported modules; you can add to the set by adding to ``autoreload.files``:: spyne.util.autorel.AutoReloader.FILES.add(myFile) spyne.util.autorel.AutoReloader.match = r'^(?!cherrypy).+' The autoreload plugin takes a ``frequency`` argument. The default is 1 second; that is, the autoreloader will examine files once each second. """ FILES = set() """The set of files to poll for modifications.""" def __init__(self, frequency=1, match='.*'): self.max_cloexec_files = MAX_FILES self.mtimes = {} self.files = set(AutoReloader.FILES) self.match = match """A regular expression by which to match filenames. If there are imported files you do *not* wish to monitor, you can adjust the ``match`` attribute, a regular expression. For example, to stop monitoring cherrypy itself, try ``match=r'^(?!cherrypy).+'``\\. """ self.frequency = frequency """The interval in seconds at which to poll for modified files.""" def start(self): from twisted.internet.task import LoopingCall retval = LoopingCall(self.run) retval.start(self.frequency) return retval # oh no def sysfiles(self): """Return a Set of sys.modules filenames to monitor.""" files = set() for k, m in list(sys.modules.items()): if re.match(self.match, k): if ( hasattr(m, '__loader__') and hasattr(m.__loader__, 'archive') ): f = m.__loader__.archive else: try: f = getattr(m, '__file__', None) except ImportError: f = None if f is not None and not os.path.isabs(f): # ensure absolute paths so a os.chdir() in the app # doesn't break me f = os.path.normpath( os.path.join(_module__file__base, f)) files.add(f) return files def run(self): """Reload the process if registered files have been modified.""" for filename in self.sysfiles() | self.files: if filename: if filename.endswith('.pyc'): filename = filename[:-1] oldtime = self.mtimes.get(filename, 0) if oldtime is None: # Module with no .py file. Skip it. continue try: mtime = os.stat(filename).st_mtime except OSError: # Either a module with no .py file, or it's been deleted. mtime = None if filename not in self.mtimes: # If a module has no .py file, this will be None. self.mtimes[filename] = mtime else: if mtime is None or mtime > oldtime: # The file has been deleted or modified. logger.info("Restarting because '%s' has changed." % filename) from twisted.internet import reactor reactor.stop() self._do_execv() return @staticmethod def _extend_pythonpath(env): """ If sys.path[0] is an empty string, the interpreter was likely invoked with -m and the effective path is about to change on re-exec. Add the current directory to $PYTHONPATH to ensure that the new process sees the same path. This issue cannot be addressed in the general case because Python cannot reliably reconstruct the original command line (http://bugs.python.org/issue14208). (This idea filched from tornado.autoreload) """ path_prefix = '.' + os.pathsep existing_path = env.get('PYTHONPATH', '') needs_patch = ( sys.path[0] == '' and not existing_path.startswith(path_prefix) ) if needs_patch: env["PYTHONPATH"] = path_prefix + existing_path def _set_cloexec(self): """Set the CLOEXEC flag on all open files (except stdin/out/err). If self.max_cloexec_files is an integer (the default), then on platforms which support it, it represents the max open files setting for the operating system. This function will be called just before the process is restarted via os.execv() to prevent open files from persisting into the new process. Set self.max_cloexec_files to 0 to disable this behavior. """ for fd in range(3, self.max_cloexec_files): # skip stdin/out/err try: flags = fcntl.fcntl(fd, fcntl.F_GETFD) except IOError: continue fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) def _do_execv(self): """Re-execute the current process. This must be called from the main thread, because certain platforms (OS X) don't allow execv to be called in a child thread very well. """ args = sys.argv[:] self._extend_pythonpath(os.environ) logger.info('Re-spawning %s' % ' '.join(args)) logger.info("") logger.info("%s Bye! %s", YEL("-" * 35), YEL("-" * 35)) logger.info("") if sys.platform[:4] == 'java': from _systemrestart import SystemRestart raise SystemRestart args.insert(0, sys.executable) if sys.platform == 'win32': args = ['"%s"' % arg for arg in args] os.chdir(_module__file__base) logger.debug("Change working directory to: %s", _module__file__base) if self.max_cloexec_files: self._set_cloexec() os.execv(sys.executable, args) spyne-spyne-2.14.0/spyne/util/cdict.py000066400000000000000000000047141417664205300176610ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """cdict (ClassDict) is a funny kind of dict that tries to return the values for the base classes of a key when the entry for the key is not found. It is not a generalized dictionary that can handle any type of key -- it relies on spyne.model api to look for classes. It also assumes cdict never changes after the first lookup. >>> from spyne.util.cdict import cdict >>> class A(object): ... pass ... >>> class B(A): ... pass ... >>> class C(object): ... pass ... >>> class D: ... pass ... >>> d=cdict({A: "fun", object: "base"}) >>> print d[A] fun >>> print d {: 'fun', : 'base'} >>> print d[B] fun >>> print d {: 'fun', : 'fun', : 'base'} >>> print d[C] base >>> print d {: 'fun', : 'fun', : 'base', : 'base'} >>> print d[D] *** KeyError: >>> """ import logging logger = logging.getLogger(__name__) class cdict(dict): def __getitem__(self, cls): try: return dict.__getitem__(self, cls) except KeyError as e: if not hasattr(cls, '__bases__'): cls = cls.__class__ for b in reversed(cls.__bases__): try: retval = self[b] # this is why a cdict instance must never be modified after # the first lookup self[cls] = retval return retval except KeyError: pass raise e def get(self, k, d=None): try: return self[k] except KeyError: return d spyne-spyne-2.14.0/spyne/util/cherry.py000066400000000000000000000024631417664205300200660ustar00rootroot00000000000000# Use Cherrypy as wsgi server. # Source: https://www.digitalocean.com/community/tutorials/how-to-deploy-python-wsgi-applications-using-a-cherrypy-web-server-behind-nginx import logging import cherrypy def cherry_graft_and_start(wsgi_application, host="0.0.0.0", port=8080, num_threads=30, ssl_module=None, cert=None, key=None, cacert=None): logging.basicConfig(level=logging.DEBUG) logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) # Mount the application cherrypy.tree.graft(wsgi_application, "/") # Unsubscribe the default server cherrypy.server.unsubscribe() # Instantiate a new server object server = cherrypy._cpserver.Server() # Configure the server object server.socket_host = host server.socket_port = port server.thread_pool = num_threads # For SSL Support if ssl_module is not None: server.ssl_module = ssl_module # eg. 'pyopenssl' server.ssl_certificate = cert # eg. 'ssl/certificate.crt' server.ssl_private_key = key # eg. 'ssl/private.key' server.ssl_certificate_chain = cacert # eg. 'ssl/bundle.crt' # Subscribe this server server.subscribe() # Start the server engine (Option 1 *and* 2) cherrypy.engine.start() return cherrypy.engine.block() spyne-spyne-2.14.0/spyne/util/color.py000066400000000000000000000056621417664205300177140ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import try: import colorama R = lambda s: ''.join((colorama.Fore.RED, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) G = lambda s: ''.join((colorama.Fore.GREEN, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) B = lambda s: ''.join((colorama.Fore.BLUE, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) DARK_R = lambda s: ''.join((colorama.Fore.RED, s, colorama.Style.RESET_ALL)) DARK_G = lambda s: ''.join((colorama.Fore.GREEN, s, colorama.Style.RESET_ALL)) DARK_B = lambda s: ''.join((colorama.Fore.BLUE, s, colorama.Style.RESET_ALL)) YEL = lambda s: ''.join((colorama.Fore.YELLOW, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) MAG = lambda s: ''.join((colorama.Fore.MAGENTA, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) CYA = lambda s: ''.join((colorama.Fore.CYAN, colorama.Style.BRIGHT, s, colorama.Style.RESET_ALL)) DARK_YEL = lambda s: ''.join((colorama.Fore.YELLOW, s, colorama.Style.RESET_ALL)) DARK_MAG = lambda s: ''.join((colorama.Fore.MAGENTA, s, colorama.Style.RESET_ALL)) DARK_CYA = lambda s: ''.join((colorama.Fore.CYAN, s, colorama.Style.RESET_ALL)) except ImportError: R = lambda s: s G = lambda s: s B = lambda s: s DARK_R = lambda s: s DARK_G = lambda s: s DARK_B = lambda s: s YEL = lambda s: s MAG = lambda s: s CYA = lambda s: s DARK_YEL = lambda s: s DARK_MAG = lambda s: s DARK_CYA = lambda s: s if __name__ == '__main__': print(R("RED")) print(G("GREEN")) print(B("BLUE")) print(DARK_R("DARK_RED")) print(DARK_G("DARK_GREEN")) print(DARK_B("DARK_BLUE")) print(YEL("YELLOW")) print(MAG("MAGENTA")) print(CYA("CYAN")) spyne-spyne-2.14.0/spyne/util/coopmt.py000066400000000000000000000052461417664205300200750ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The cooperative multitasking module. It includes the coroutine stuff. This could have been named just coroutine.py if it wasn't for the coroutine decorator. """ import logging logger = logging.getLogger(__name__) from itertools import chain from inspect import isgeneratorfunction class Break(Exception): """Raised for breaking out of infinite loops inside coroutines.""" pass def coroutine(func): assert isgeneratorfunction(func) def start(*args, **kwargs): try: ret = func(*args, **kwargs) except TypeError as e: logger.error("Function %r at %s:%d got error %r", func.func_name, func.__module__, func.__code__.co_firstlineno, e) raise try: next(ret) except StopIteration: return None except Exception as e: if not hasattr(e, 'logged'): logger.error("Exception in coroutine") logger.exception(e) try: e.logged = True except: pass raise return ret return start def keepfirst(func): assert isgeneratorfunction(func) def start(*args, **kwargs): try: ret = func(*args, **kwargs) except TypeError as e: logger.error("Function %r at %s:%d got error %r", func.func_name, func.__module__, func.__code__.co_firstlineno, e) raise try: first = next(ret) except StopIteration: return None except Exception as e: if not hasattr(e, 'logged'): logger.error("Exception in coroutine") logger.exception(e) try: e.logged = True except: pass raise return chain((first,), ret) return start spyne-spyne-2.14.0/spyne/util/dictdoc.py000066400000000000000000000171621417664205300202050ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from spyne.context import FakeContext from spyne.protocol.dictdoc import HierDictDocument from spyne.protocol.dictdoc import SimpleDictDocument try: from spyne.protocol.json import JsonDocument except ImportError as _import_error: _local_import_error = _import_error def JsonDocument(*args, **kwargs): raise _local_import_error try: from spyne.protocol.yaml import YamlDocument except ImportError as _import_error: _local_import_error = _import_error def YamlDocument(*args, **kwargs): raise _local_import_error try: from spyne.protocol.msgpack import MessagePackDocument except ImportError as _import_error: _local_import_error = _import_error def MessagePackDocument(*args, **kwargs): raise _local_import_error from spyne.model.primitive import Double from spyne.model.primitive import Boolean from spyne.model.primitive import Decimal from spyne.model.primitive import Integer class _UtilProtocol(HierDictDocument): def __init__(self, app=None, validator=None, mime_type=None, ignore_uncap=False, # DictDocument specific ignore_wrappers=True, complex_as=dict, ordered=False): super(_UtilProtocol, self).__init__(app, validator, mime_type, ignore_uncap, ignore_wrappers, complex_as, ordered) self._from_unicode_handlers[Double] = lambda cls, val: val self._from_unicode_handlers[Boolean] = lambda cls, val: val self._from_unicode_handlers[Decimal] = lambda cls, val: val self._from_unicode_handlers[Integer] = lambda cls, val: val self._to_unicode_handlers[Double] = lambda cls, val: val self._to_unicode_handlers[Boolean] = lambda cls, val: val self._to_unicode_handlers[Decimal] = lambda cls, val: val self._to_unicode_handlers[Integer] = lambda cls, val: val def get_doc_as_object(d, cls, ignore_wrappers=True, complex_as=list, protocol=_UtilProtocol, protocol_inst=None): if protocol_inst is None: protocol_inst = protocol(ignore_wrappers=ignore_wrappers, complex_as=complex_as) return protocol_inst._doc_to_object(None, cls, d) get_dict_as_object = get_doc_as_object """DEPRECATED: Use ``get_doc_as_object`` instead""" def get_object_as_doc(o, cls=None, ignore_wrappers=True, complex_as=dict, protocol=_UtilProtocol, protocol_inst=None): if cls is None: cls = o.__class__ if protocol_inst is None: protocol_inst = protocol(ignore_wrappers=ignore_wrappers, complex_as=complex_as) retval = protocol_inst._object_to_doc(cls, o) if not ignore_wrappers: return {cls.get_type_name(): retval} return retval get_object_as_dict = get_object_as_doc """DEPRECATED: Use ``get_object_as_doc`` instead.""" def get_object_as_simple_dict(o, cls=None, hier_delim='.', prefix=None): if cls is None: cls = o.__class__ return SimpleDictDocument(hier_delim=hier_delim) \ .object_to_simple_dict(cls, o, prefix=prefix) def get_object_as_json(o, cls=None, ignore_wrappers=True, complex_as=list, encoding='utf8', polymorphic=False, indent=None, **kwargs): if cls is None: cls = o.__class__ prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic, indent=indent, **kwargs) ctx = FakeContext(out_document=[prot._object_to_doc(cls, o)]) prot.create_out_string(ctx, encoding) return b''.join(ctx.out_string) def get_object_as_json_doc(o, cls=None, ignore_wrappers=True, complex_as=list, polymorphic=False, indent=None, **kwargs): if cls is None: cls = o.__class__ prot = JsonDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic, indent=indent, **kwargs) return prot._object_to_doc(cls, o) def get_object_as_yaml(o, cls=None, ignore_wrappers=False, complex_as=dict, encoding='utf8', polymorphic=False): if cls is None: cls = o.__class__ prot = YamlDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) ctx = FakeContext(out_document=[prot._object_to_doc(cls,o)]) prot.create_out_string(ctx, encoding) return b''.join(ctx.out_string) def get_object_as_yaml_doc(o, cls=None, ignore_wrappers=False, complex_as=dict, polymorphic=False): if cls is None: cls = o.__class__ prot = YamlDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) return prot._object_to_doc(cls, o) def get_object_as_msgpack(o, cls=None, ignore_wrappers=False, complex_as=dict, polymorphic=False): if cls is None: cls = o.__class__ prot = MessagePackDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) ctx = FakeContext(out_document=[prot._object_to_doc(cls,o)]) prot.create_out_string(ctx) return b''.join(ctx.out_string) def get_object_as_msgpack_doc(o, cls=None, ignore_wrappers=False, complex_as=dict, polymorphic=False): if cls is None: cls = o.__class__ prot = MessagePackDocument(ignore_wrappers=ignore_wrappers, complex_as=complex_as, polymorphic=polymorphic) return prot._object_to_doc(cls, o) def json_loads(s, cls, protocol=JsonDocument, **kwargs): if s is None: return None if s == '': return None prot = protocol(**kwargs) ctx = FakeContext(in_string=[s]) prot.create_in_document(ctx) return prot._doc_to_object(None, cls, ctx.in_document, validator=prot.validator) get_json_as_object = json_loads def yaml_loads(s, cls, protocol=YamlDocument, ignore_wrappers=False, **kwargs): if s is None: return None if s == '' or s == b'': return None prot = protocol(ignore_wrappers=ignore_wrappers, **kwargs) ctx = FakeContext(in_string=[s]) prot.create_in_document(ctx) retval = prot._doc_to_object(None, cls, ctx.in_document, validator=prot.validator) return retval get_yaml_as_object = yaml_loads spyne-spyne-2.14.0/spyne/util/django.py000066400000000000000000000434041417664205300200340ustar00rootroot00000000000000# encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Useful stuff to integrate Spyne with Django. * Django model <-> spyne type mapping * Service for common exception handling """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import re from itertools import chain from django.core.exceptions import (ImproperlyConfigured, ObjectDoesNotExist, ValidationError as DjValidationError) from django.core.validators import (slug_re, MinLengthValidator, MaxLengthValidator) try: from django.core.validators import comma_separated_int_list_re except ImportError: comma_separated_int_list_re = re.compile(r'^[\d,]+$') from spyne.error import (ResourceNotFoundError, ValidationError as BaseValidationError, Fault) from spyne.model import primitive from spyne.model.complex import ComplexModelMeta, ComplexModelBase from spyne.service import Service from spyne.util.cdict import cdict from spyne.util.odict import odict from spyne.util.six import add_metaclass # regex is based on http://www.w3.org/TR/xforms20/#xforms:email email_re = re.compile( r"[A-Za-z0-9!#-'\*\+\-/=\?\^_`\{-~]+" r"(\.[A-Za-z0-9!#-'\*\+\-/=\?\^_`\{-~]+)*@" # domain part is either a single symbol r"(" # or have at least two symbols # hyphen can't be at the beginning or end of domain part # domain should contain at least 2 parts, the last one is TLD r"[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+" # TLD should contain only letters, at least 2 r"[A-Za-z]{2,}", re.IGNORECASE) def _handle_minlength(validator, params): new_min = validator.limit_value old_min = params.setdefault('min_len', new_min) params['min_len'] = max(old_min, new_min) def _handle_maxlength(validator, params): new_max = validator.limit_value old_max = params.setdefault('max_len', new_max) params['max_len'] = min(old_max, new_max) class BaseDjangoFieldMapper(object): """Abstrace base class for field mappers.""" _VALIDATOR_HANDLERS = cdict({ MinLengthValidator: _handle_minlength, MaxLengthValidator: _handle_maxlength, }) @staticmethod def is_field_nullable(field, **kwargs): """Return True if django field is nullable.""" return field.null @staticmethod def is_field_blank(field, **kwargs): """Return True if django field is blank.""" return field.blank def map(self, field, **kwargs): """Map field to spyne model. :param field: Django Field instance :param kwargs: Extra params to configure spyne model :returns: tuple (field attribute name, mapped spyne model) """ params = kwargs.copy() self._process_validators(field.validators, params) nullable = self.is_field_nullable(field, **kwargs) blank = self.is_field_blank(field, **kwargs) required = not (field.has_default() or blank or field.primary_key) if field.has_default(): params['default'] = field.get_default() spyne_model = self.get_spyne_model(field, **kwargs) customized_model = spyne_model(nullable=nullable, min_occurs=int(required), **params) return (field.attname, customized_model) def get_spyne_model(self, field, **kwargs): """Return spyne model for given Django field.""" raise NotImplementedError def _process_validators(self, validators, params): for v in validators: handler = self._VALIDATOR_HANDLERS.get(type(v)) if handler: handler(v, params) class DjangoFieldMapper(BaseDjangoFieldMapper): """Basic mapper for django fields.""" def __init__(self, spyne_model): """Django field mapper constructor.""" self.spyne_model = spyne_model def get_spyne_model(self, field, **kwargs): """Return configured spyne model.""" return self.spyne_model class DecimalMapper(DjangoFieldMapper): """Mapper for DecimalField.""" def map(self, field, **kwargs): """Map DecimalField to spyne model. :returns: tuple (field attribute name, mapped spyne model) """ params = kwargs.copy() params.update({ 'total_digits': field.max_digits, 'fraction_digits': field.decimal_places, }) return super(DecimalMapper, self).map(field, **params) class RelationMapper(BaseDjangoFieldMapper): """Mapper for relation fields (ForeignKey, OneToOneField).""" def __init__(self, django_model_mapper): """Constructor for relation field mapper.""" self.django_model_mapper = django_model_mapper @staticmethod def is_field_blank(field, **kwargs): """Return True if `optional_relations` is set. Otherwise use basic behaviour. """ optional_relations = kwargs.get('optional_relations', False) return (optional_relations or BaseDjangoFieldMapper.is_field_blank(field, **kwargs)) def get_spyne_model(self, field, **kwargs): """Return spyne model configured by related field.""" related_field = field.rel.get_related_field() if hasattr(field, 'rel') else field.remote_field.get_related_field() field_type = related_field.__class__.__name__ field_mapper = self.django_model_mapper.get_field_mapper(field_type) _, related_spyne_model = field_mapper.map(related_field, **kwargs) return related_spyne_model class DjangoModelMapper(object): r"""Mapper from django models to spyne complex models. You can extend it registering new field types: :: class NullBooleanMapper(DjangoFieldMapper): def map(self, field, **kwargs): params = kwargs.copy() # your mapping logic goes here return super(NullBooleanMapper, self).map(field, **params) default_model_mapper.register_field_mapper('NullBooleanField', \ NullBooleanMapper(primitive.Boolean)) You may subclass it if you want different mapping logic for different Django models. """ field_mapper_class = DjangoFieldMapper class UnknownFieldMapperException(Exception): """Raises when there is no field mapper for given django_type.""" def __init__(self, django_spyne_models=()): """Register field mappers in internal registry.""" self._registry = {} for django_type, spyne_model in django_spyne_models: self.register(django_type, spyne_model) def get_field_mapper(self, django_type): """Get mapper registered for given django_type. :param django_type: Django internal field type :returns: registered mapper :raises: :exc:`UnknownFieldMapperException` """ try: return self._registry[django_type] except KeyError: raise self.UnknownFieldMapperException( 'No mapper for field type {0}'.format(django_type)) def register(self, django_type, spyne_model): """Register default field mapper for django_type and spyne_model. :param django_type: Django internal field type :param spyne_model: Spyne model, usually primitive """ field_mapper = self.field_mapper_class(spyne_model) self.register_field_mapper(django_type, field_mapper) def register_field_mapper(self, django_type, field_mapper): """Register field mapper for django_type. :param django_type: Django internal field type :param field_mapper: :class:`DjangoFieldMapper` instance """ self._registry[django_type] = field_mapper @staticmethod def get_all_field_names(meta): if hasattr(meta, 'get_all_field_names'): return meta.get_all_field_names() return list(set(chain.from_iterable( (field.name, field.attname) if hasattr(field, 'attname') else ( field.name,) for field in meta.get_fields() # For complete backwards compatibility, you may want to exclude # GenericForeignKey from the results. if not (field.many_to_one and field.related_model is None) ))) @staticmethod def _get_fields(django_model, exclude=None): field_names = set(exclude) if exclude is not None else set() meta = django_model._meta # pylint: disable=W0212 unknown_fields_names = \ field_names.difference(DjangoModelMapper.get_all_field_names(meta)) if unknown_fields_names: raise ImproperlyConfigured( 'Unknown field names: {0}' .format(', '.join(unknown_fields_names))) return [field for field in meta.fields if field.name not in field_names] def map(self, django_model, exclude=None, **kwargs): """Prepare dict of model fields mapped to spyne models. :param django_model: Django model class. :param exclude: list of fields excluded from mapping. :param kwargs: extra kwargs are passed to all field mappers :returns: dict mapping attribute names to spyne models :raises: :exc:`UnknownFieldMapperException` """ field_map = odict() for field in self._get_fields(django_model, exclude): field_type = field.__class__.__name__ try: field_mapper = self._registry[field_type] except KeyError: # mapper for this field is not registered if not (field.has_default() or field.null): # field is required raise self.UnknownFieldMapperException( 'No mapper for field type {0}'.format(field_type)) else: # skip this field logger.info('Field {0} is skipped from mapping.') continue attr_name, spyne_model = field_mapper.map(field, **kwargs) field_map[attr_name] = spyne_model return field_map def strip_regex_metachars(pattern): """Strip ^ and $ from pattern begining and end. According to http://www.w3.org/TR/xmlschema-0/#regexAppendix XMLSchema expression language does not contain the metacharacters ^ and $. :returns: stripped pattern string """ start = 0 till = len(pattern) if pattern.startswith('^'): start = 1 if pattern.endswith('$'): till -= 1 return pattern[start:till] # django's own slug_re.pattern is invalid according to xml schema -- it doesn't # like the location of the dash character. using the equivalent pattern accepted # by xml schema here. SLUG_RE_PATTERN = '[a-zA-Z0-9_-]+' DEFAULT_FIELD_MAP = ( ('AutoField', primitive.Integer32), ('CharField', primitive.NormalizedString), ('SlugField', primitive.Unicode( type_name='Slug', pattern=strip_regex_metachars(SLUG_RE_PATTERN))), ('TextField', primitive.Unicode), ('EmailField', primitive.Unicode( type_name='Email', pattern=strip_regex_metachars(email_re.pattern))), ('CommaSeparatedIntegerField', primitive.Unicode( type_name='CommaSeparatedField', pattern=strip_regex_metachars(comma_separated_int_list_re.pattern))), ('URLField', primitive.AnyUri), ('FilePathField', primitive.Unicode), ('BooleanField', primitive.Boolean), ('NullBooleanField', primitive.Boolean), ('IntegerField', primitive.Integer), ('BigIntegerField', primitive.Integer64), ('PositiveIntegerField', primitive.UnsignedInteger32), ('SmallIntegerField', primitive.Integer16), ('PositiveSmallIntegerField', primitive.UnsignedInteger16), ('FloatField', primitive.Double), ('TimeField', primitive.Time), ('DateField', primitive.Date), ('DateTimeField', primitive.DateTime), # simple fixed defaults for relation fields ('ForeignKey', primitive.Integer32), ('OneToOneField', primitive.Integer32), ) def model_mapper_factory(mapper_class, field_map): """Factory for model mappers. The factory is useful to create custom field mappers based on default one. """ model_mapper = mapper_class(field_map) # register relation field mappers that are aware of related field type model_mapper.register_field_mapper( 'ForeignKey', RelationMapper(model_mapper)) model_mapper.register_field_mapper( 'OneToOneField', RelationMapper(model_mapper)) model_mapper.register_field_mapper('DecimalField', DecimalMapper(primitive.Decimal)) return model_mapper default_model_mapper = model_mapper_factory(DjangoModelMapper, DEFAULT_FIELD_MAP) class DjangoComplexModelMeta(ComplexModelMeta): """Meta class for complex spyne models representing Django models.""" def __new__(mcs, name, bases, attrs): # pylint: disable=C0202 """Populate new complex type from configured Django model.""" super_new = super(DjangoComplexModelMeta, mcs).__new__ abstract = bool(attrs.get('__abstract__', False)) if abstract: # skip processing of abstract models return super_new(mcs, name, bases, attrs) attributes = attrs.get('Attributes') if attributes is None: raise ImproperlyConfigured('You have to define Attributes and ' 'specify Attributes.django_model') if getattr(attributes, 'django_model', None) is None: raise ImproperlyConfigured('You have to define django_model ' 'attribute in Attributes') mapper = getattr(attributes, 'django_mapper', default_model_mapper) attributes.django_mapper = mapper exclude = getattr(attributes, 'django_exclude', None) optional_relations = getattr(attributes, 'django_optional_relations', False) spyne_attrs = mapper.map(attributes.django_model, exclude=exclude, optional_relations=optional_relations) spyne_attrs.update(attrs) return super_new(mcs, name, bases, spyne_attrs) @add_metaclass(DjangoComplexModelMeta) class DjangoComplexModel(ComplexModelBase): """Base class with Django model mapping support. Sample usage: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person Attribute :attr:`django_model` is required for Django model mapping machinery. You can customize your types defining custom type fields: :: class PersonType(DjangoComplexModel): gender = primitive.Unicode(pattern='^[FM]$') class Attributes(DjangoComplexModel.Attributes): django_model = Person There is an option to specify custom mapper: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person django_mapper = my_custom_mapper You can also exclude some fields from mapping: :: class PersonType(DjangoComplexModel): class Attributes(DjangoComplexModel.Attributes): django_model = Person django_exclude = ['phone'] You may set `django_optional_relations`` attribute flag to indicate that relation fields (ForeignKey, OneToOneField) of your model are optional. This is useful when you want to create base and related instances in remote procedure. In this case primary key of base model is not yet available. """ __abstract__ = True class ObjectNotFoundError(ResourceNotFoundError): """Fault constructed from `model.DoesNotExist` exception.""" def __init__(self, does_not_exist_exc): """Construct fault with code Client.NotFound.""" message = str(does_not_exist_exc) object_name = message.split()[0] # we do not want to reuse initialization of ResourceNotFoundError Fault.__init__( self, faultcode='Client.{0}NotFound'.format(object_name), faultstring=message) class ValidationError(BaseValidationError): """Fault constructed from `ValidationError` exception.""" def __init__(self, validation_error_exc): """Construct fault with code Client..""" message = str(validation_error_exc) # we do not want to reuse initialization of BaseValidationError Fault.__init__( self, faultcode='Client.{0}'.format( type(validation_error_exc).__name__), faultstring=message) class DjangoService(Service): """Service with common Django exception handling.""" @classmethod def call_wrapper(cls, ctx): """Handle common Django exceptions.""" try: out_object = super(DjangoService, cls).call_wrapper(ctx) except ObjectDoesNotExist as e: raise ObjectNotFoundError(e) except DjValidationError as e: raise ValidationError(e) return out_object # FIXME: To be removed in Spyne 3 DjangoServiceBase = DjangoService spyne-spyne-2.14.0/spyne/util/dyninit.py000066400000000000000000000101261417664205300202430ustar00rootroot00000000000000# encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from datetime import date, datetime from spyne import D, Integer, ModelBase, Date, DateTime, IpAddress, Decimal, \ Boolean from spyne.protocol import ProtocolBase from spyne.util import six from spyne.util.cdict import cdict BOOL_VALUES_BYTES_TRUE = (b't', b'1', b'on', b'yes', b'true') BOOL_VALUES_STR_TRUE = (u't', u'1', u'on', u'yes', u'true') BOOL_VALUES_BYTES_FALSE = (b'f', b'0', b'off', b'no', b'false') BOOL_VALUES_STR_FALSE = (u'f', u'0', u'off', u'no', u'false') BOOL_VALUES_NONE = (None, '') if six.PY2: bytes = str else: unicode = str _prot = ProtocolBase() def _bool_from_int(i): if i in (0, 1): return i == 1 raise ValueError(i) def _bool_from_bytes(s): if s in BOOL_VALUES_NONE: return None s = s.strip() if s in BOOL_VALUES_NONE: return None s = s.lower() if s in BOOL_VALUES_BYTES_TRUE: return True if s in BOOL_VALUES_BYTES_FALSE: return False raise ValueError(s) def _bool_from_str(s): if s in BOOL_VALUES_NONE: return None s = s.strip() if s in BOOL_VALUES_NONE: return None if s in BOOL_VALUES_STR_TRUE: return True if s in BOOL_VALUES_STR_FALSE: return False raise ValueError(s) MAP = cdict({ ModelBase: cdict({ object: lambda _: _, bytes: lambda _: _.strip(), unicode: lambda _: _.strip(), }), Decimal: cdict({ D: lambda d: d, int: lambda i: D(i), bytes: lambda s: None if s.strip() == '' else D(s.strip()), unicode: lambda s: None if s.strip() == u'' else D(s.strip()), }), Boolean: cdict({ D: lambda d: _bool_from_int(int(d)), int: _bool_from_int, bytes: _bool_from_bytes, unicode: _bool_from_str, }), Integer: cdict({ D: lambda _: _, int: lambda _: _, bytes: lambda s: None if s.strip() == '' else int(s.strip()), unicode: lambda s: None if s.strip() == u'' else int(s.strip()), }), Date: cdict({ date: lambda _: _, datetime: lambda _: _.date(), object: lambda _:_, bytes: lambda s: None if s.strip() in ('', '0000-00-00') else _prot.date_from_unicode(Date, s.strip()), unicode: lambda s: None if s.strip() in (u'', u'0000-00-00') else _prot.date_from_unicode(Date, s.strip()), }), DateTime: cdict({ date: lambda _: datetime(date.year, date.month, date.day), datetime: lambda _: _, object: lambda _:_, bytes: lambda s: None if s.strip() in ('', '0000-00-00 00:00:00') else _prot.datetime_from_unicode(DateTime, s.strip()), unicode: lambda s: None if s.strip() in (u'', u'0000-00-00 00:00:00') else _prot.datetime_from_unicode(DateTime, s.strip()), }), IpAddress: cdict({ object: lambda _: _, bytes: lambda s: None if s.strip() == '' else s.strip(), unicode: lambda s: None if s.strip() == u'' else s.strip(), }) }) def dynamic_init(cls, **kwargs): fti = cls.get_flat_type_info(cls) retval = cls() for k, v in fti.items(): if k in kwargs: subval = kwargs[k] t = MAP[v] setattr(retval, k, t[type(subval)](subval)) return retval spyne-spyne-2.14.0/spyne/util/email.py000066400000000000000000000115371417664205300176630ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from __future__ import absolute_import import logging logger = logging.getLogger(__name__) import getpass import inspect import traceback import smtplib import mimetypes from socket import gethostname from subprocess import Popen, PIPE from email.utils import COMMASPACE, formatdate from email import message_from_string from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.message import MIMEMessage from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.mime.nonmultipart import MIMENonMultipart from email.encoders import encode_base64 from spyne.util import six def email_exception(exception_address, message="", bcc=None): # http://stackoverflow.com/questions/1095601/find-module-name-of-the-originating-exception-in-python frm = inspect.trace()[-1] mod = inspect.getmodule(frm[0]) module_name = mod.__name__ if mod else frm[1] sender = 'robot@spyne.io' recipients = [exception_address] if bcc is not None: recipients.extend(bcc) error_str = ("%s\n\n%s" % (message, traceback.format_exc())) msg = MIMEText(error_str.encode('utf8'), 'plain', 'utf8') msg['To'] = exception_address msg['From'] = 'Spyne ' msg['Date'] = formatdate() msg['Subject'] = "(%s@%s) %s" % (getpass.getuser(), gethostname(), module_name) try: smtp_object = smtplib.SMTP('localhost') smtp_object.sendmail(sender, recipients, msg.as_string()) logger.error("Error email sent") except Exception as e: logger.error("Error: unable to send email") logger.exception(e) def email_text_smtp(addresses, sender=None, subject='', message="", host='localhost', port=25): if sender is None: sender = 'Spyne ' exc = traceback.format_exc() if exc is not None: message = (u"%s\n\n%s" % (message, exc)) msg = MIMEText(message.encode('utf8'), 'plain', 'utf8') msg['To'] = COMMASPACE.join(addresses) msg['From'] = sender msg['Date'] = formatdate() msg['Subject'] = subject smtp_object = smtplib.SMTP(host, port) if six.PY2: smtp_object.sendmail(sender, addresses, msg.as_string()) else: smtp_object.sendmail(sender, addresses, msg.as_bytes()) logger.info("Text email sent to: %r.", addresses) def email_text(addresses, sender=None, subject="", message="", bcc=None, att=None): if att is None: att = {} if sender is None: sender = 'Spyne ' exc = traceback.format_exc() if exc is not None and exc != 'None\n' and exc != 'NoneType: None\n': message = (u"%s\n\n%s" % (message, exc)) msg = MIMEText(message.encode('utf8'), 'plain', 'utf8') if len(att) > 0: newmsg = MIMEMultipart() newmsg.attach(msg) for k, v in att.items(): mime_type, encoding = mimetypes.guess_type(k) if mime_type == "message/rfc822": part = MIMEMessage(message_from_string(v)) elif mime_type.startswith("image/"): part = MIMEImage(v, mime_type.rsplit('/', 1)[-1]) elif mime_type is not None: mime_type_main, mime_type_sub = mime_type.split('/', 1) part = MIMENonMultipart(mime_type_main, mime_type_sub) part.set_payload(v) encode_base64(part) else: part = MIMEApplication(v) newmsg.attach(part) part.add_header('Content-Disposition', 'attachment', filename=k) msg = newmsg msg['To'] = COMMASPACE.join(addresses) msg['From'] = sender msg['Date'] = formatdate() msg['Subject'] = subject cmd = ["/usr/sbin/sendmail", "-oi", '--'] cmd.extend(addresses) if bcc is not None: cmd.extend(bcc) p = Popen(cmd, stdin=PIPE) if six.PY2: p.communicate(msg.as_string()) else: p.communicate(msg.as_bytes()) logger.info("Text email sent to: %r.", addresses) spyne-spyne-2.14.0/spyne/util/etreeconv.py000066400000000000000000000101121417664205300205520ustar00rootroot00000000000000# # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """This module contains the utility methods that convert an ElementTree hierarchy to python dicts and vice versa. """ try: from collections.abc import Sized except ImportError: # Python 2 from collections import Sized from lxml import etree from spyne.util import six from spyne.util.odict import odict def root_dict_to_etree(d): """Converts a dictionary to an xml hiearchy. Just like a valid xml document, the dictionary must have a single element. The format of the child dictionaries is the same as :func:`dict_to_etree`. """ assert len(d) == 1, "Incoming dict len must be exactly 1. Data: %r" % d key, = d.keys() retval = etree.Element(key) for val in d.values(): break if val is None: return retval if isinstance(val, dict) or isinstance(val, odict): dict_to_etree(val, retval) elif not isinstance(val, Sized) or isinstance(val, six.string_types): retval.text = str(val) else: for a in val: dict_to_etree(a, retval) return retval def dict_to_etree(d, parent): """Takes a the dict whose value is either None or an instance of dict, odict or an iterable. The iterables can contain either other dicts/odicts or str/unicode instances. """ for k, v in d.items(): if v is None: etree.SubElement(parent, k) elif isinstance(v, six.string_types): etree.SubElement(parent, k).text = v elif isinstance(v, dict) or isinstance(v, odict): child = etree.SubElement(parent, k) dict_to_etree(v, child) elif not isinstance(v, Sized): etree.SubElement(parent, k).text = str(v) elif len(v) == 0: etree.SubElement(parent, k) else: for e in v: child = etree.SubElement(parent, k) if isinstance(e, dict) or isinstance(e, odict): dict_to_etree(e, child) else: child.text = str(e) def root_etree_to_dict(element, iterable=(list, list.append)): """Takes an xml root element and returns the corresponding dict. The second argument is a pair of iterable type and the function used to add elements to the iterable. The xml attributes are ignored. """ return {element.tag: iterable[0]([etree_to_dict(element, iterable)])} def etree_to_dict(element, iterable=(list, list.append)): """Takes an xml root element and returns the corresponding dict. The second argument is a pair of iterable type and the function used to add elements to the iterable. The xml attributes are ignored. """ if (element.text is None) or element.text.isspace(): retval = odict() for elt in element: if not (elt.tag in retval): retval[elt.tag] = iterable[0]() iterable[1](retval[elt.tag], etree_to_dict(elt, iterable)) else: retval = element.text return retval def etree_strip_namespaces(element): """Removes any namespace information form the given element recursively.""" retval = etree.Element(element.tag.rpartition('}')[-1]) retval.text = element.text for a in element.attrib: retval.attrib[a.rpartition('}')[-1]] = element.attrib[a] for e in element: retval.append(etree_strip_namespaces(e)) return retval spyne-spyne-2.14.0/spyne/util/fileproxy.py000066400000000000000000000137521417664205300206160ustar00rootroot00000000000000 # # Copyright (C) 2013-2014 by Hong Minhee # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # import os from spyne.util.six.moves.collections_abc import Iterator __all__ = 'FileProxy', 'ReusableFileProxy', 'SeekableFileProxy' class FileProxy(Iterator): """The complete proxy for ``wrapped`` file-like object. :param wrapped: the file object to wrap :type wrapped: :class:`file`, file-like object """ def __init__(self, wrapped): self.wrapped = wrapped self.mmap = None def __iter__(self): f = self.wrapped it = getattr(f, '__iter__', None) if callable(it): return it() return self def __next__(self): """Implementation of :class:`collections.Iterator` protocol.""" line = self.readline() if not line: raise StopIteration('hit eof') return line next = __next__ def read(self, size=-1): """Reads at the most ``size`` bytes from the file. It maybe less if the read hits EOF before obtaining ``size`` bytes. :param size: bytes to read. if it is negative or omitted, read all data until EOF is reached. default is -1 :returns: read bytes. an empty string when EOF is encountered immediately :rtype: :class:`str` """ return self.wrapped.read(size) def readline(self, size=None): r"""Reads an entire line from the file. A trailing newline character is kept in the string (but maybe absent when a file ends with an incomplete line). :param size: if it's present and non-negative, it is maximum byte count (including trailing newline) and an incomplete line maybe returned :type size: :class:`numbers.Integral` :returns: read bytes :rtype: :class:`str` .. note:: Unlike ``stdio``'s :c:func:`fgets()`, the returned string contains null characters (``'\0'``) if they occurred in the input. """ return self.wrapped.readline(size) def readlines(self, sizehint=None): """Reads until EOF using :meth:`readline()`. :param sizehint: if it's present, instead of reading up to EOF, whole lines totalling approximately ``sizehint`` bytes (or more to accommodate a final whole line) :type sizehint: :class:`numbers.Integral` :returns: a list containing the lines read :rtype: :class:`list` """ wrapped = self.wrapped try: readlines = wrapped.readlines except AttributeError: lines = [] while 1: line = wrapped.readline() if line: lines.append(line) else: break return lines return readlines() if sizehint is None else readlines(sizehint) def xreadlines(self): """The same to ``iter(file)``. Use that. .. deprecated:: long time ago Use :func:`iter()` instead. """ return iter(self) def close(self): """Closes the file. It's a context manager as well, so prefer :keyword:`with` statement than direct call of this:: with FileProxy(file_) as f: print f.read() """ try: close = self.wrapped.close except AttributeError: pass else: close() def __enter__(self): return self.wrapped def __exit__(self, exc_type, value, traceback): self.close() def __del__(self): if self.mmap is not None: self.mmap.close() self.wrapped.close() def fileno(self): return self.wrapped.fileno() class SeekableFileProxy(FileProxy): """The almost same to :class:`FileProxy` except it has :meth:`seek()` and :meth:`tell()` methods in addition. """ def seek(self, offset, whence=os.SEEK_SET): """Sets the file's current position. :param offset: the offset to set :type offset: :class:`numbers.Integral` :param whence: see the docs of :meth:`file.seek()`. default is :const:`os.SEEK_SET` """ self.wrapped.seek(offset, whence) def tell(self): """Gets the file's current position. :returns: the file's current position :rtype: :class:`numbers.Integral` """ return self.wrapped.tell() class ReusableFileProxy(SeekableFileProxy): """It memorizes the current position (:meth:`tell()`) when the context enters and then rewinds (:meth:`seek()`) back to the memorized :attr:`initial_offset` when the context exits. """ def __enter__(self): self.initial_offset = self.tell() self.seek(0) return super(ReusableFileProxy, self).__enter__() def __exit__(self, exc_type, value, traceback): self.seek(self.initial_offset) spyne-spyne-2.14.0/spyne/util/gencpp.py000066400000000000000000000160121417664205300200410ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """A PoC that implements like 2% of the job of converting Spyne objects to standard C++ classes.""" import sys INDENT = ' ' class Object(object): def __init__(self): self.parent = None self.comment_before = None self.comment_after = None def _comment_before_to_stream(self, ostr, indent): if self.comment_before is None: return ostr.write("\n") ostr.write(INDENT * indent) ostr.write("/**\n") ostr.write(INDENT * indent) ostr.write(" *") for line in self.comment_before.split('\n'): ostr.write(" ") ostr.write(line) ostr.write('\n') ostr.write(INDENT * indent) ostr.write(" */") ostr.write("\n") def _comment_after_to_stream(self, ostr, indent): if self.comment_after is None: return lines = self.comment_after.split('\n') if len(lines) < 2: ostr.write(" // ") ostr.write(self.comment_after) else: ostr.write(INDENT * indent) ostr.write("/**\n") ostr.write(INDENT * indent) ostr.write(" *") for line in lines: ostr.write(" ") ostr.write(line) ostr.write('\n') ostr.write(INDENT * indent) ostr.write(" */") ostr.write("\n") class Entry(Object): def __init__(self, modifier=None): super(Entry, self).__init__() self.modifier = modifier def to_decl_stream(self, ostr, indent): raise NotImplemented() def to_defn_stream(self, ostr, indent): raise NotImplemented() class Literal(Object): def __init__(self, value): super(Literal, self).__init__() self.value = value class StringLiteral(Literal): def to_stream(self, ostr, indent): self._comment_before_to_stream(ostr, indent) ostr.write('"') ostr.write(self.value) # TODO: escaping ostr.write('"') self._comment_after_to_stream(ostr, indent) class DataMember(Entry): def __init__(self, modifier, type, name, initializer=None): super(DataMember, self).__init__(modifier) self.type = type self.name = name self.initializer = initializer def to_decl_stream(self, ostr, indent): ostr.write(INDENT * indent) if self.modifier is not None: ostr.write(self.modifier) ostr.write(" ") ostr.write(self.type) ostr.write(" ") ostr.write(self.name) if self.modifier != 'static' and self.initializer is not None: ostr.write(" = ") self.initializer.to_stream(ostr, indent) ostr.write(";") ostr.write("\n") def to_defn_stream(self, ostr, indent): if self.modifier != 'static': return self._comment_before_to_stream(ostr, indent) ostr.write(INDENT * indent) ostr.write(self.type) ostr.write(" ") parents = [] parent = self.parent while parent is not None: parents.insert(0, parent) parent = parent.parent for parent in parents: ostr.write(parent.name) ostr.write("::") ostr.write(self.name) if self.initializer is not None: ostr.write(" = ") self.initializer.to_stream(ostr, indent) ostr.write(";") ostr.write("\n") self._comment_after_to_stream(ostr, indent) class Class(Entry): def __init__(self): super(Class, self).__init__() self.name = None self.namespace = None self.type = 'class' self.public_entries = [] self.protected_entries = [] self.private_entries = [] def to_decl_stream(self, ostr, indent=0): if self.namespace is not None: ostr.write("namespace ") ostr.write(self.namespace) ostr.write(" {\n") ostr.write(INDENT * indent) ostr.write("%s %s {\n" % (self.type, self.name,)) if len(self.public_entries) > 0: ostr.write(INDENT * indent) ostr.write("public:\n") for e in self.public_entries: e.to_decl_stream(ostr, indent + 1) ostr.write("\n") if len(self.protected_entries) > 0: ostr.write(INDENT * indent) ostr.write("protected:\n") for e in self.protected_entries: e.to_decl_stream(ostr, indent + 1) ostr.write("\n") if len(self.private_entries) > 0: ostr.write(INDENT * indent) ostr.write("private:\n") for e in self.private_entries: e.to_decl_stream(ostr, indent + 1) ostr.write("\n") ostr.write(INDENT * indent) ostr.write("};\n") if self.namespace is not None: ostr.write("}\n") def to_defn_stream(self, ostr, indent=0): if self.namespace is not None: ostr.write("namespace ") ostr.write(self.namespace) ostr.write(" {\n") if len(self.public_entries) > 0: for e in self.public_entries: e.to_defn_stream(ostr, indent) if len(self.protected_entries) > 0: for e in self.protected_entries: e.to_defn_stream(ostr, indent) if len(self.private_entries) > 0: for e in self.private_entries: e.to_defn_stream(ostr, indent) if self.namespace is not None: ostr.write("}\n") def gen_cpp_class(cls, namespace=None, type_map=None): if type_map is None: type_map = dict() ocls = Class() ocls.name = cls.get_type_name() ocls.namespace = namespace keys = Class() keys.name = "Key" keys.parent = ocls keys.type = "struct" ocls.public_entries.append(keys) for k, v in cls.get_flat_type_info(cls).items(): member = DataMember( "static", "const std::string", k, StringLiteral(v.Attributes.sub_name or k) ) member.comment_before = v.Annotations.doc member.parent = keys keys.public_entries.append(member) ocls.to_decl_stream(sys.stdout) sys.stdout.write("\n\n\n\n") ocls.to_defn_stream(sys.stdout) spyne-spyne-2.14.0/spyne/util/http.py000066400000000000000000000045301417664205300175460ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import sys import time from time import strftime from time import gmtime from collections import deque from spyne.util import six if six.PY2: COOKIE_MAX_AGE = sys.maxint else: COOKIE_MAX_AGE = sys.maxsize # This is a modified version of twisted's addCookie def generate_cookie(k, v, max_age=None, domain=None, path=None, comment=None, secure=False): """Generate a HTTP response cookie. No sanity check whatsoever is done, don't send anything other than ASCII. :param k: Cookie key. :param v: Cookie value. :param max_age: Seconds. :param domain: Domain. :param path: Path. :param comment: Whatever. :param secure: If true, appends 'Secure' to the cookie string. """ if not six.PY2 and isinstance(v, bytes): v = v.decode("ascii") retval = deque(['%s=%s' % (k, v)]) if max_age is not None: retval.append("Max-Age=%d" % max_age) assert time.time() < COOKIE_MAX_AGE expires = time.time() + max_age expires = min(2<<30, expires) - 1 # FIXME retval.append("Expires=%s" % strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(expires))) if domain is not None: retval.append("Domain=%s" % domain) if path is not None: retval.append("Path=%s" % path) if comment is not None: retval.append("Comment=%s" % comment) if secure: retval.append("Secure") return '; '.join(retval) spyne-spyne-2.14.0/spyne/util/invregexp.py000066400000000000000000000212771417664205300206050ustar00rootroot00000000000000 # # invRegex.py # # Copyright 2008, Paul McGuire # # The pyparsing license follows: # ######### # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ######### # # pyparsing script to expand a regular expression into all possible matching # strings # # Supports: # - {n} and {m,n} repetition, but not unbounded + or * repetition # - ? optional elements # - [] character ranges # - () grouping # - | alternation # __all__ = ["count", "invregexp"] from pyparsing import Combine from pyparsing import Literal from pyparsing import ParseFatalException from pyparsing import ParseResults from pyparsing import ParserElement from pyparsing import SkipTo from pyparsing import Suppress from pyparsing import Word from pyparsing import nums from pyparsing import oneOf from pyparsing import opAssoc from pyparsing import operatorPrecedence from pyparsing import printables from pyparsing import srange class CharacterRangeEmitter(object): def __init__(self, chars): # remove duplicate chars in character range, but preserve original order seen = set() self.charset = "".join(seen.add(c) or c for c in chars if c not in seen) def __str__(self): return '[' + self.charset + ']' def __repr__(self): return '[' + self.charset + ']' def make_generator(self): def gen_chars(): for s in self.charset: yield s return gen_chars class OptionalEmitter(object): def __init__(self, expr): self.expr = expr def make_generator(self): def optional_gen(): yield "" for s in self.expr.make_generator()(): yield s return optional_gen class DotEmitter(object): def make_generator(self): def dot_gen(): for c in printables: yield c return dot_gen class GroupEmitter(object): def __init__(self, exprs): self.exprs = ParseResults(exprs) def make_generator(self): def group_gen(): def recurse_list(elist): if len(elist) == 1: for s in elist[0].make_generator()(): yield s else: for s in elist[0].make_generator()(): for s2 in recurse_list(elist[1:]): yield s + s2 if self.exprs: for s in recurse_list(self.exprs): yield s return group_gen class AlternativeEmitter(object): def __init__(self, exprs): self.exprs = exprs def make_generator(self): def alt_gen(): for e in self.exprs: for s in e.make_generator()(): yield s return alt_gen class LiteralEmitter(object): def __init__(self, lit): self.lit = lit def __str__(self): return "Lit:" + self.lit def __repr__(self): return "Lit:" + self.lit def make_generator(self): def lit_gen(): yield self.lit return lit_gen def handle_range(toks): return CharacterRangeEmitter(srange(toks[0])) def handle_repetition(toks): toks = toks[0] if toks[1] in "*+": raise ParseFatalException("", 0, "unbounded repetition operators not supported") if toks[1] == "?": return OptionalEmitter(toks[0]) if "count" in toks: return GroupEmitter([toks[0]] * int(toks.count)) if "minCount" in toks: mincount = int(toks.minCount) maxcount = int(toks.maxCount) optcount = maxcount - mincount if optcount: opt = OptionalEmitter(toks[0]) for i in range(1, optcount): opt = OptionalEmitter(GroupEmitter([toks[0], opt])) return GroupEmitter([toks[0]] * mincount + [opt]) else: return [toks[0]] * mincount def handle_literal(toks): lit = "" for t in toks: if t[0] == "\\": if t[1] == "t": lit += '\t' else: lit += t[1] else: lit += t return LiteralEmitter(lit) def handle_macro(toks): macroChar = toks[0][1] if macroChar == "d": return CharacterRangeEmitter("0123456789") elif macroChar == "w": return CharacterRangeEmitter(srange("[A-Za-z0-9_]")) elif macroChar == "s": return LiteralEmitter(" ") else: raise ParseFatalException("", 0, "unsupported macro character (" + macroChar + ")") def handle_sequence(toks): return GroupEmitter(toks[0]) def handle_dot(): return CharacterRangeEmitter(printables) def handle_alternative(toks): return AlternativeEmitter(toks[0]) _parser = None def parser(): global _parser if _parser is None: ParserElement.setDefaultWhitespaceChars("") lbrack, rbrack, lbrace, rbrace, lparen, rparen = map(Literal, "[]{}()") reMacro = Combine("\\" + oneOf(list("dws"))) escapedChar = ~ reMacro + Combine("\\" + oneOf(list(printables))) reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t" reRange = Combine(lbrack + SkipTo(rbrack, ignore=escapedChar) + rbrack) reLiteral = (escapedChar | oneOf(list(reLiteralChar))) reDot = Literal(".") repetition = ( (lbrace + Word(nums).setResultsName("count") + rbrace) | (lbrace + Word(nums).setResultsName("minCount") + "," + Word(nums).setResultsName("maxCount") + rbrace) | oneOf(list("*+?")) ) reRange.setParseAction(handle_range) reLiteral.setParseAction(handle_literal) reMacro.setParseAction(handle_macro) reDot.setParseAction(handle_dot) reTerm = (reLiteral | reRange | reMacro | reDot) reExpr = operatorPrecedence(reTerm, [ (repetition, 1, opAssoc.LEFT, handle_repetition), (None, 2, opAssoc.LEFT, handle_sequence), (Suppress('|'), 2, opAssoc.LEFT, handle_alternative), ]) _parser = reExpr return _parser def count(gen): """Simple function to count the number of elements returned by a generator.""" i = 0 for s in gen: i += 1 return i def invregexp(regex): """Call this routine as a generator to return all the strings that match the input regular expression. for s in invregexp("[A-Z]{3}\d{3}"): print s """ invReGenerator = GroupEmitter(parser().parseString(regex)).make_generator() return invReGenerator() def main(): tests = r""" [A-EA] [A-D]* [A-D]{3} X[A-C]{3}Y X[A-C]{3}\( X\d foobar\d\d foobar{2} foobar{2,9} fooba[rz]{2} (foobar){2} ([01]\d)|(2[0-5]) ([01]\d\d)|(2[0-4]\d)|(25[0-5]) [A-C]{1,2} [A-C]{0,3} [A-C]\s[A-C]\s[A-C] [A-C]\s?[A-C][A-C] [A-C]\s([A-C][A-C]) [A-C]\s([A-C][A-C])? [A-C]{2}\d{2} @|TH[12] @(@|TH[12])? @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))? @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))? (([ECMP]|HA|AK)[SD]|HS)T [A-CV]{2} A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr] (a|b)|(x|y) (a|b) (x|y) """.split('\n') for t in tests: t = t.strip() if not t: continue print('-' * 50) print(t) try: print(count(invregexp(t))) for s in invregexp(t): print(s) except ParseFatalException as pfe: print(pfe.msg) print() continue print() if __name__ == "__main__": main() spyne-spyne-2.14.0/spyne/util/memo.py000066400000000000000000000130611417664205300175230ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The module for memoization stuff. When you have memory leaks in your daemon, the reason could very well be reckless usage of the tools here. These are NOT thread-safe. If you are relying on exactly-one-execution-per-key behavior in a multithreaded environment, roll your own stuff. """ import logging logger = logging.getLogger(__name__) import threading MEMOIZATION_STATS_LOG_INTERVAL = 60.0 def _log_all(): logger.info("%d memoizers", len(memoize.registry)) for memo in memoize.registry: logger.info("%r: %d entries.", memo.func, len(memo.memo)) def _log_func(func): for memo in memoize.registry: if memo.func is func.func.im_self.func: break else: logger.error("%r not found in memoization regisry", func) return logger.info("%r: %d entries.", memo.func, len(memo.memo)) for k, v in memo.memo.items(): logger.info("\t%r: %r", k, v) def start_memoization_stats_logger(func=None): logger.info("Enabling @memoize statistics every %d second(s).", MEMOIZATION_STATS_LOG_INTERVAL) if func is None: _log_all() else: _log_func(func) t = threading.Timer(MEMOIZATION_STATS_LOG_INTERVAL, start_memoization_stats_logger, (func,)) t.daemon = True t.start() class memoize(object): """A memoization decorator that keeps caching until reset.""" registry = [] def __init__(self, func): self.func = func self.memo = {} self.lock = threading.RLock() memoize.registry.append(self) def __call__(self, *args, **kwargs): key = self.get_key(args, kwargs) # we hope that gil makes this comparison is race-free if not key in self.memo: with self.lock: # make sure the situation hasn't changed after lock acq if not key in self.memo: value = self.func(*args, **kwargs) self.memo[key] = value return value return self.memo.get(key) def get_key(self, args, kwargs): return tuple(args), tuple(kwargs.items()) def reset(self): self.memo = {} class memoize_first(object): """A memoization decorator that keeps the first call without condition, aka a singleton accessor.""" registry = [] def __init__(self, func): self.func = func self.lock = threading.RLock() memoize.registry.append(self) def __call__(self, *args, **kwargs): if not hasattr(self, 'memo'): value = self.func(*args, **kwargs) self.memo = value return value return self.memo def reset(self): del self.memo def memoize_ignore(values): """A memoization decorator that does memoization unless the returned value is in the 'values' iterable. eg let `values = (2,)` and `add = lambda x, y: x + y`, the result of `add(1, 1)` (=2) is not memoized but the result of `add(5, 5)` (=10) is. """ assert iter(values), \ "memoize_ignore requires an iterable of values to ignore" class _memoize_ignored(memoize): def __call__(self, *args, **kwargs): key = self.get_key(args, kwargs) # we hope that gil makes this comparison is race-free if not key in self.memo: with self.lock: # make sure the situation hasn't changed after lock acq if not key in self.memo: value = self.func(*args, **kwargs) if not value in values: self.memo[key] = value return value return self.memo.get(key) return _memoize_ignored class memoize_ignore_none(memoize): """A memoization decorator that ignores `None` values. ie when the decorated function returns `None`, the value is returned but not memoized. """ def __call__(self, *args, **kwargs): key = self.get_key(args, kwargs) # we hope that gil makes this comparison is race-free if not key in self.memo: with self.lock: # make sure the situation hasn't changed after lock acq if not key in self.memo: value = self.func(*args, **kwargs) if not (value is None): self.memo[key] = value return value return self.memo.get(key) class memoize_id(memoize): """A memoization decorator that keeps caching until reset for unhashable types. It works on id()'s of objects instead.""" def get_key(self, args, kwargs): return tuple([id(a) for a in args]), \ tuple([(k, id(v)) for k, v in kwargs.items()]) spyne-spyne-2.14.0/spyne/util/meta.py000066400000000000000000000121721417664205300175160ustar00rootroot00000000000000# encoding: utf-8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Metaclass utilities for :attr:`spyne.model.complex.ComplexModelBase.Attributes.declare_order` """ import sys import inspect from functools import wraps from itertools import chain from warnings import warn from spyne.util.odict import odict class ClassNotFoundException(Exception): """Raise when class declaration is not found in frame stack.""" class AttributeNotFoundException(Exception): """Raise when attribute is not found in class declaration.""" class Prepareable(type): """Implement __prepare__ for Python 2. This class is used in Python 2 and Python 3 to support `six.add_metaclass` decorator that populates attributes of resulting class from plain unordered attributes dict of decorated class. Based on https://gist.github.com/DasIch/5562625 """ def __new__(cls, name, bases, attributes): try: constructor = attributes["__new__"] except KeyError: return type.__new__(cls, name, bases, attributes) def preparing_constructor(cls, name, bases, attributes): # Don't bother with this shit unless the user *explicitly* asked for # it for c in chain(bases, [cls]): if hasattr(c,'Attributes') and not \ (c.Attributes.declare_order in (None, 'random')): break else: return constructor(cls, name, bases, attributes) try: cls.__prepare__ except AttributeError: return constructor(cls, name, bases, attributes) if isinstance(attributes, odict): # we create class dynamically with passed odict return constructor(cls, name, bases, attributes) current_frame = sys._getframe() class_declaration = None while class_declaration is None: literals = list(reversed(current_frame.f_code.co_consts)) for literal in literals: if inspect.iscode(literal) and literal.co_name == name: class_declaration = literal break else: if current_frame.f_back: current_frame = current_frame.f_back else: raise ClassNotFoundException( "Can't find class declaration in any frame") def get_index(attribute_name, _names=class_declaration.co_names): try: return _names.index(attribute_name) except ValueError: if attribute_name.startswith('_'): # we don't care about the order of magic and non # public attributes return 0 else: msg = ("Can't find {0} in {1} class declaration. " .format(attribute_name, class_declaration.co_name)) msg += ("HINT: use spyne.util.odict.odict for " "class attributes if you populate them" " dynamically.") raise AttributeNotFoundException(msg) by_appearance = sorted( attributes.items(), key=lambda item: get_index(item[0]) ) namespace = cls.__prepare__(name, bases) for key, value in by_appearance: namespace[key] = value new_cls = constructor(cls, name, bases, namespace) found_module = inspect.getmodule(class_declaration) assert found_module is not None, ( 'Module is not found for class_declaration {0}, name {1}' .format(class_declaration, name)) assert found_module.__name__ == new_cls.__module__, ( 'Found wrong class declaration of {0}: {1} != {2}.' .format(name, found_module.__name__, new_cls.__module__)) return new_cls try: attributes["__new__"] = wraps(constructor)(preparing_constructor) except: warn("Wrapping class initializer failed. This is normal " "when running under Nuitka") return type.__new__(cls, name, bases, attributes) spyne-spyne-2.14.0/spyne/util/odict.py000066400000000000000000000075421417664205300176770ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ This module contains an ordered dictionary implementation. We need this in Python 2.7 because collections.OrderedDict does not support reordering by assignment to keys(). We need this in Python 3.x because keys() returns KeyView which which doesn't support `__getitem__` -- i.e. getting nth variable from the ordered dict. """ class odict(dict): """Sort of an ordered dictionary implementation.""" def __init__(self, data=()): if isinstance(data, self.__class__): self.__list = list(data.__list) super(odict, self).__init__(data) else: self.__list = [] super(odict, self).__init__() self.update(data) def __getitem__(self, key): if isinstance(key, int): return super(odict, self).__getitem__(self.__list[key]) else: return super(odict, self).__getitem__(key) def __setitem__(self, key, val): if isinstance(key, int): super(odict, self).__setitem__(self.__list[key], val) else: if not (key in self): self.__list.append(key) super(odict, self).__setitem__(key, val) assert len(self.__list) == super(odict, self).__len__(), ( repr(self.__list), super(odict, self).__repr__()) def __repr__(self): return "{%s}" % ','.join(["%r: %r" % (k, v) for k, v in self.items()]) def __str__(self): return repr(self) def __len__(self): assert len(self.__list) == super(odict, self).__len__() return len(self.__list) def __iter__(self): return iter(self.__list) def __delitem__(self, key): if not isinstance(key, int): super(odict, self).__delitem__(key) key = self.__list.index(key) # ouch. else: super(odict, self).__delitem__(self.__list[key]) del self.__list[key] def __add__(self, other): self.update(other) return self def items(self): retval = [] for k in self.__list: retval.append( (k, super(odict, self).__getitem__(k)) ) return retval def iteritems(self): for k in self.__list: yield k, super(odict, self).__getitem__(k) def keys(self): return self.__list def update(self, data, **kwargs): if isinstance(data, (dict, odict)): data = data.items() for k, v in data: self[k] = v for k, v in kwargs.items(): self[k] = v def values(self): retval = [] for l in self.__list: retval.append(super(odict, self).__getitem__(l)) return retval def itervalues(self): for l in self.__list: yield self[l] def get(self, key, default=None): if key in self: return self[key] return default def append(self, t): k, v = t self[k] = v def insert(self, index, item): k, v = item if k in self: del self.__list[self.__list.index(k)] self.__list.insert(index, k) super(odict, self).__setitem__(k, v) spyne-spyne-2.14.0/spyne/util/oset.py000066400000000000000000000047421417664205300175460ustar00rootroot00000000000000# http://code.activestate.com/recipes/576694/ from spyne.util.six.moves.collections_abc import MutableSet KEY, PREV, NEXT = list(range(3)) """This module contains an ordered set implementation from http://code.activestate.com/recipes/576694/ """ class oset(MutableSet): """An ordered set implementation.""" def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[PREV] curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] def extend(self, keys): for key in keys: if key not in self.map: end = self.end curr = end[PREV] curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] def discard(self, key): if key in self.map: key, prev, next = self.map.pop(key) prev[NEXT] = next next[PREV] = prev def __iter__(self): end = self.end curr = end[NEXT] while curr is not end: yield curr[KEY] curr = curr[NEXT] def __reversed__(self): end = self.end curr = end[PREV] while curr is not end: yield curr[KEY] curr = curr[PREV] def pop(self, last=True): if not self: raise KeyError('set is empty') key = next(reversed(self)) if last else next(iter(self)) self.discard(key) return key def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) def __eq__(self, other): if isinstance(other, oset): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) @property def back(self): return self.end[1][0] if __name__ == '__main__': print((oset('abracadabra'))) stuff = oset() stuff.add(1) print(stuff) stuff.add(1) print(stuff) print((oset('simsalabim'))) o = oset('abcde') print(o) print(o.end) o = oset() print(o.back) o = oset([3]) print(o.back) spyne-spyne-2.14.0/spyne/util/protocol.py000066400000000000000000000024331417664205300204300ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Helpers for protocol boilerplate.""" from spyne import MethodContext from spyne.server import ServerBase def deserialize_request_string(string, app): """Deserialize request string using in_protocol in application definition. Returns the corresponding native python object. """ server = ServerBase(app) initial_ctx = MethodContext(server, MethodContext.SERVER) initial_ctx.in_string = [string] ctx = server.generate_contexts(initial_ctx)[0] server.get_in_object(ctx) return ctx.in_object spyne-spyne-2.14.0/spyne/util/resource.py000066400000000000000000000037301417664205300204170ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # import os.path import spyne.util.autorel def get_resource_path(ns, fn): try: from spyne._deploymentinfo import resource_filename except ImportError: from pkg_resources import resource_filename resfn = resource_filename(ns, fn) spyne.util.autorel.AutoReloader.FILES.add(resfn) path = os.path.abspath(resfn) return path def get_resource_file(ns, fn): return open(get_resource_path(ns, fn), 'rb') def get_resource_file_contents(ns, fn, enc=None): resfn = get_resource_path(ns, fn) if enc is None: return open(resfn, 'rb').read() else: return open(resfn, 'rb').read().decode(enc) def parse_xml_resource(ns, fn): from lxml import etree retval = etree.parse(get_resource_file(ns, fn)) return retval.getroot() def parse_html_resource(ns, fn): from lxml import html retval = html.parse(get_resource_file(ns, fn)) return retval.getroot() def parse_cloth_resource(ns, fn): from lxml import html retval = html.fragment_fromstring(get_resource_file_contents(ns, fn), create_parent='spyne-root') retval.attrib['spyne-tagbag'] = '' return retval spyne-spyne-2.14.0/spyne/util/simple.py000066400000000000000000000041361417664205300200620ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """Contains functions that implement the most common protocol and transport combinations""" from spyne.application import Application def wsgi_soap11_application(services, tns='spyne.simple.soap', validator=None, name=None): """Wraps `services` argument inside a WsgiApplication that uses Soap 1.1 for both input and output protocols. """ from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication application = Application(services, tns, name=name, in_protocol=Soap11(validator=validator), out_protocol=Soap11()) return WsgiApplication(application) wsgi_soap_application = wsgi_soap11_application """DEPRECATED! Use :func:`wsgi_soap11_application` instead.""" def pyramid_soap11_application(services, tns='spyne.simple.soap', validator=None, name=None): """Wraps `services` argument inside a PyramidApplication that uses Soap 1.1 for both input and output protocols. """ from spyne.protocol.soap import Soap11 from spyne.server.pyramid import PyramidApplication application = Application(services, tns, name=name, in_protocol=Soap11(validator=validator), out_protocol=Soap11()) return PyramidApplication(application) spyne-spyne-2.14.0/spyne/util/six.py000066400000000000000000001027741417664205300174030ustar00rootroot00000000000000# Copyright (c) 2010-2020 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """Utilities for writing code that runs on Python 2 and 3""" from __future__ import absolute_import import functools import itertools import operator import sys import types __author__ = "Benjamin Peterson " __version__ = "1.14.0" # Useful for very coarse version differentiation. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) if PY3: string_types = str, integer_types = int, class_types = type, text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str if sys.platform.startswith("java"): # Jython always uses 32 bits. MAXSIZE = int((1 << 31) - 1) else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): def __len__(self): return 1 << 31 try: len(X()) except OverflowError: # 32-bit MAXSIZE = int((1 << 31) - 1) else: # 64-bit MAXSIZE = int((1 << 63) - 1) del X def _add_doc(func, doc): """Add documentation to a function.""" func.__doc__ = doc def _import_module(name): """Import module, returning the module after the last dot.""" __import__(name) return sys.modules[name] class _LazyDescr(object): def __init__(self, name): self.name = name def __get__(self, obj, tp): result = self._resolve() setattr(obj, self.name, result) # Invokes __set__. try: # This is a bit ugly, but it avoids running this again by # removing this descriptor. delattr(obj.__class__, self.name) except AttributeError: pass return result class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: if new is None: new = name self.mod = new else: self.mod = old def _resolve(self): return _import_module(self.mod) def __getattr__(self, attr): _module = self._resolve() value = getattr(_module, attr) setattr(self, attr, value) return value class _LazyModule(types.ModuleType): def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): attrs = ["__doc__", "__name__"] attrs += [attr.name for attr in self._moved_attributes] return attrs # Subclasses should override this _moved_attributes = [] class MovedAttribute(_LazyDescr): def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: if new_mod is None: new_mod = name self.mod = new_mod if new_attr is None: if old_attr is None: new_attr = name else: new_attr = old_attr self.attr = new_attr else: self.mod = old_mod if old_attr is None: old_attr = name self.attr = old_attr def _resolve(self): module = _import_module(self.mod) return getattr(module, self.attr) class _SixMetaPathImporter(object): """ A meta path importer to import six.moves and its submodules. This class implements a PEP302 finder and loader. It should be compatible with Python 2.5 and all existing versions of Python3 """ def __init__(self, six_module_name): self.name = six_module_name self.known_modules = {} def _add_module(self, mod, *fullnames): for fullname in fullnames: self.known_modules[self.name + "." + fullname] = mod def _get_module(self, fullname): return self.known_modules[self.name + "." + fullname] def find_module(self, fullname, path=None): if fullname in self.known_modules: return self return None def __get_module(self, fullname): try: return self.known_modules[fullname] except KeyError: raise ImportError("This loader does not know module " + fullname) def load_module(self, fullname): try: # in case of a reload return sys.modules[fullname] except KeyError: pass mod = self.__get_module(fullname) if isinstance(mod, MovedModule): mod = mod._resolve() else: mod.__loader__ = self sys.modules[fullname] = mod return mod def is_package(self, fullname): """ Return true, if the named module is a package. We need this method to get correct spec objects with Python 3.4 (see PEP451) """ return hasattr(self.__get_module(fullname), "__path__") def get_code(self, fullname): """Return None Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None get_source = get_code # same as get_code _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" __path__ = [] # mark as package _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), MovedAttribute("getoutput", "commands", "subprocess"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), MovedAttribute("UserDict", "UserDict", "collections"), MovedAttribute("UserList", "UserList", "collections"), MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), MovedModule("cPickle", "cPickle", "pickle"), MovedModule("queue", "Queue"), MovedModule("reprlib", "repr"), MovedModule("socketserver", "SocketServer"), MovedModule("_thread", "thread", "_thread"), MovedModule("tkinter", "Tkinter"), MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), MovedModule("tkinter_tix", "Tix", "tkinter.tix"), MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), ] # Add windows specific modules. if sys.platform == "win32": _moved_attributes += [ MovedModule("winreg", "_winreg"), ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) if isinstance(attr, MovedModule): _importer._add_module(attr, "moves." + attr.name) del attr _MovedItems._moved_attributes = _moved_attributes moves = _MovedItems(__name__ + ".moves") _importer._add_module(moves, "moves") class Module_six_moves_urllib_parse(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_parse""" _urllib_parse_moved_attributes = [ MovedAttribute("ParseResult", "urlparse", "urllib.parse"), MovedAttribute("SplitResult", "urlparse", "urllib.parse"), MovedAttribute("parse_qs", "urlparse", "urllib.parse"), MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), MovedAttribute("urldefrag", "urlparse", "urllib.parse"), MovedAttribute("urljoin", "urlparse", "urllib.parse"), MovedAttribute("urlparse", "urlparse", "urllib.parse"), MovedAttribute("urlsplit", "urlparse", "urllib.parse"), MovedAttribute("urlunparse", "urlparse", "urllib.parse"), MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), MovedAttribute("quote", "urllib", "urllib.parse"), MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), MovedAttribute("splitvalue", "urllib", "urllib.parse"), MovedAttribute("splittype", "urllib", "urllib.parse"), MovedAttribute("splithost", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), MovedAttribute("uses_query", "urlparse", "urllib.parse"), MovedAttribute("uses_relative", "urlparse", "urllib.parse"), ] for attr in _urllib_parse_moved_attributes: setattr(Module_six_moves_urllib_parse, attr.name, attr) del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), "moves.urllib_parse", "moves.urllib.parse") class Module_six_moves_urllib_error(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_error""" _urllib_error_moved_attributes = [ MovedAttribute("URLError", "urllib2", "urllib.error"), MovedAttribute("HTTPError", "urllib2", "urllib.error"), MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), ] for attr in _urllib_error_moved_attributes: setattr(Module_six_moves_urllib_error, attr.name, attr) del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), "moves.urllib_error", "moves.urllib.error") class Module_six_moves_urllib_request(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_request""" _urllib_request_moved_attributes = [ MovedAttribute("urlopen", "urllib2", "urllib.request"), MovedAttribute("install_opener", "urllib2", "urllib.request"), MovedAttribute("build_opener", "urllib2", "urllib.request"), MovedAttribute("pathname2url", "urllib", "urllib.request"), MovedAttribute("url2pathname", "urllib", "urllib.request"), MovedAttribute("getproxies", "urllib", "urllib.request"), MovedAttribute("Request", "urllib2", "urllib.request"), MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), MovedAttribute("BaseHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), MovedAttribute("FileHandler", "urllib2", "urllib.request"), MovedAttribute("FTPHandler", "urllib2", "urllib.request"), MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), MovedAttribute("urlretrieve", "urllib", "urllib.request"), MovedAttribute("urlcleanup", "urllib", "urllib.request"), MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), MovedAttribute("parse_http_list", "urllib2", "urllib.request"), MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), "moves.urllib_request", "moves.urllib.request") class Module_six_moves_urllib_response(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_response""" _urllib_response_moved_attributes = [ MovedAttribute("addbase", "urllib", "urllib.response"), MovedAttribute("addclosehook", "urllib", "urllib.response"), MovedAttribute("addinfo", "urllib", "urllib.response"), MovedAttribute("addinfourl", "urllib", "urllib.response"), ] for attr in _urllib_response_moved_attributes: setattr(Module_six_moves_urllib_response, attr.name, attr) del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), "moves.urllib_response", "moves.urllib.response") class Module_six_moves_urllib_robotparser(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_robotparser""" _urllib_robotparser_moved_attributes = [ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), ] for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), "moves.urllib_robotparser", "moves.urllib.robotparser") class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") request = _importer._get_module("moves.urllib_request") response = _importer._get_module("moves.urllib_response") robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): return ['parse', 'error', 'request', 'response', 'robotparser'] _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib") def add_move(move): """Add an item to six.moves.""" setattr(_MovedItems, move.name, move) def remove_move(name): """Remove item from six.moves.""" try: delattr(_MovedItems, name) except AttributeError: try: del moves.__dict__[name] except KeyError: raise AttributeError("no such move, %r" % (name,)) if PY3: _meth_func = "__func__" _meth_self = "__self__" _func_closure = "__closure__" _func_code = "__code__" _func_defaults = "__defaults__" _func_globals = "__globals__" _func_name = "__name__" else: _meth_func = "im_func" _meth_self = "im_self" _func_closure = "func_closure" _func_code = "func_code" _func_defaults = "func_defaults" _func_globals = "func_globals" _func_name = "func_name" try: advance_iterator = next except NameError: def advance_iterator(it): return it.next() next = advance_iterator try: callable = callable except NameError: def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: def get_unbound_function(unbound): return unbound create_bound_method = types.MethodType def create_unbound_method(func, cls): return func Iterator = object else: def get_unbound_function(unbound): return unbound.im_func def create_bound_method(func, obj): return types.MethodType(func, obj, obj.__class__) def create_unbound_method(func, cls): return types.MethodType(func, None, cls) class Iterator(object): def next(self): return type(self).__next__(self) callable = callable _add_doc(get_unbound_function, """Get the function out of a possibly unbound function""") get_method_function = operator.attrgetter(_meth_func) get_method_self = operator.attrgetter(_meth_self) get_function_closure = operator.attrgetter(_func_closure) get_function_code = operator.attrgetter(_func_code) get_function_defaults = operator.attrgetter(_func_defaults) get_function_globals = operator.attrgetter(_func_globals) get_function_name = operator.attrgetter(_func_name) if PY3: def iterkeys(d, **kw): return iter(d.keys(**kw)) def itervalues(d, **kw): return iter(d.values(**kw)) def iteritems(d, **kw): return iter(d.items(**kw)) def iterlists(d, **kw): return iter(d.lists(**kw)) viewkeys = operator.methodcaller("keys") viewvalues = operator.methodcaller("values") viewitems = operator.methodcaller("items") else: def iterkeys(d, **kw): return d.iterkeys(**kw) def itervalues(d, **kw): return d.itervalues(**kw) def iteritems(d, **kw): return d.iteritems(**kw) def iterlists(d, **kw): return d.iterlists(**kw) viewkeys = operator.methodcaller("viewkeys") viewvalues = operator.methodcaller("viewvalues") viewitems = operator.methodcaller("viewitems") _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") _add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") _add_doc(iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary.") if PY3: def b(s): return s.encode("latin-1") def u(s): return s unichr = chr import struct int2byte = struct.Struct(">B").pack del struct byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io StringIO = io.StringIO BytesIO = io.BytesIO del io _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" _assertNotRegex = "assertNotRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" _assertNotRegex = "assertNotRegex" else: def b(s): return s # Workaround for standalone backslash def u(s): return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") unichr = unichr int2byte = chr def byte2int(bs): return ord(bs[0]) def indexbytes(buf, i): return ord(buf[i]) iterbytes = functools.partial(itertools.imap, ord) import StringIO StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" _assertNotRegex = "assertNotRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") def assertCountEqual(self, *args, **kwargs): return getattr(self, _assertCountEqual)(*args, **kwargs) def assertRaisesRegex(self, *args, **kwargs): return getattr(self, _assertRaisesRegex)(*args, **kwargs) def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) def assertNotRegex(self, *args, **kwargs): return getattr(self, _assertNotRegex)(*args, **kwargs) if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): try: if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value finally: value = None tb = None else: def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec_("""def reraise(tp, value, tb=None): try: raise tp, value, tb finally: tb = None """) if sys.version_info[:2] > (3,): exec_("""def raise_from(value, from_value): try: raise value from from_value finally: value = None """) else: def raise_from(value, from_value): raise value print_ = getattr(moves.builtins, "print", None) if print_ is None: def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) if fp is None: return def write(data): if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. if (isinstance(fp, file) and isinstance(data, unicode) and fp.encoding is not None): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: if isinstance(sep, unicode): want_unicode = True elif not isinstance(sep, str): raise TypeError("sep must be None or a string") end = kwargs.pop("end", None) if end is not None: if isinstance(end, unicode): want_unicode = True elif not isinstance(end, str): raise TypeError("end must be None or a string") if kwargs: raise TypeError("invalid keyword arguments to print()") if not want_unicode: for arg in args: if isinstance(arg, unicode): want_unicode = True break if want_unicode: newline = unicode("\n") space = unicode(" ") else: newline = "\n" space = " " if sep is None: sep = space if end is None: end = newline for i, arg in enumerate(args): if i: write(sep) write(arg) write(end) if sys.version_info[:2] < (3, 3): _print = print_ def print_(*args, **kwargs): fp = kwargs.get("file", sys.stdout) flush = kwargs.pop("flush", False) _print(*args, **kwargs) if flush and fp is not None: fp.flush() _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): # This does exactly the same what the :func:`py3:functools.update_wrapper` # function does on Python versions after 3.2. It sets the ``__wrapped__`` # attribute on ``wrapper`` object and it doesn't raise an error if any of # the attributes mentioned in ``assigned`` and ``updated`` are missing on # ``wrapped`` object. def _update_wrapper(wrapper, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): for attr in assigned: try: value = getattr(wrapped, attr) except AttributeError: continue else: setattr(wrapper, attr, value) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) wrapper.__wrapped__ = wrapped return wrapper _update_wrapper.__doc__ = functools.update_wrapper.__doc__ def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): return functools.partial(_update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) wraps.__doc__ = functools.wraps.__doc__ else: wraps = functools.wraps def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(type): def __new__(cls, name, this_bases, d): if sys.version_info[:2] >= (3, 7): # This version introduced PEP 560 that requires a bit # of extra care (we mimic what is done by __build_class__). resolved_bases = types.resolve_bases(bases) if resolved_bases is not bases: d['__orig_bases__'] = bases else: resolved_bases = bases return meta(name, resolved_bases, d) @classmethod def __prepare__(cls, name, this_bases): return meta.__prepare__(name, bases) return type.__new__(metaclass, 'temporary_class', (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) if hasattr(cls, '__qualname__'): orig_vars['__qualname__'] = cls.__qualname__ return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper def ensure_binary(s, encoding='utf-8', errors='strict'): """Coerce **s** to six.binary_type. For Python 2: - `unicode` -> encoded to `str` - `str` -> `str` For Python 3: - `str` -> encoded to `bytes` - `bytes` -> `bytes` """ if isinstance(s, text_type): return s.encode(encoding, errors) elif isinstance(s, binary_type): return s else: raise TypeError("not expecting type '%s'" % type(s)) def ensure_str(s, encoding='utf-8', errors='strict'): """Coerce *s* to `str`. For Python 2: - `unicode` -> encoded to `str` - `str` -> `str` For Python 3: - `str` -> `str` - `bytes` -> decoded to `str` """ if not isinstance(s, (text_type, binary_type)): raise TypeError("not expecting type '%s'" % type(s)) if PY2 and isinstance(s, text_type): s = s.encode(encoding, errors) elif PY3 and isinstance(s, binary_type): s = s.decode(encoding, errors) return s def ensure_text(s, encoding='utf-8', errors='strict'): """Coerce *s* to six.text_type. For Python 2: - `unicode` -> `unicode` - `str` -> `unicode` For Python 3: - `str` -> `str` - `bytes` -> decoded to `str` """ if isinstance(s, binary_type): return s.decode(encoding, errors) elif isinstance(s, text_type): return s else: raise TypeError("not expecting type '%s'" % type(s)) def python_2_unicode_compatible(klass): """ A class decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: if '__str__' not in klass.__dict__: raise ValueError("@python_2_unicode_compatible cannot be applied " "to %s because it doesn't define __str__()." % klass.__name__) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass # Complete the moves implementation. # This code is at the end of this module to speed up module loading. # Turn this module into a package. __path__ = [] # required for PEP 302 and PEP 451 __package__ = __name__ # see PEP 366 @ReservedAssignment if globals().get("__spec__") is not None: __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable # Remove other six meta path importers, since they cause problems. This can # happen if six is removed from sys.modules and then reloaded. (Setuptools does # this for some reason.) if sys.meta_path: for i, importer in enumerate(sys.meta_path): # Here's some real nastiness: Another "instance" of the six module might # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. if (type(importer).__name__ == "_SixMetaPathImporter" and importer.name == __name__): del sys.meta_path[i] break del i, importer # Finally, add the importer to the meta path import hook. sys.meta_path.append(_importer)spyne-spyne-2.14.0/spyne/util/tdict.py000066400000000000000000000054551417664205300177050ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The typed dict module""" from itertools import chain class tdict(dict): def __init__(self, kt=None, vt=None, data=None): """This is a typed dict implementation that optionally enforces given types on contained values on assignment.""" self._kt = kt self._vt = vt if kt is None and vt is None: self.check = self._check_noop elif kt is None: self.check = self._check_v elif vt is None: self.check = self._check_k else: self.check = self._check_kv if data is not None: self.update(data) def _check_noop(self, *_): pass def _check_k(self, key, _): if not isinstance(key, self._kt): raise TypeError(repr(key)) def _check_v(self, _, value): if not isinstance(value, self._vt): raise TypeError(repr(value)) def _check_kv(self, key, value): if not isinstance(key, self._kt): raise TypeError(repr(key)) if not isinstance(value, self._vt): raise TypeError(repr(value)) def __setitem__(self, key, value): self.check(key, value) super(tdict, self).__setitem__(key, value) def update(self, E=None, **F): try: it = chain(E.items(), F.items()) except AttributeError: it = chain(E, F) for k, v in it: self[k] = v def setdefault(self, k, d=None): self._check_k(k, d) if self._kt is None else None self._check_v(k, d) if self._vt is None else None super(tdict, self).setdefault(k, d) @classmethod def fromkeys(cls, S, v=None): kt = vt = None if len(S) > 0: kt, = set((type(s) for s in S)) if v is not None: vt = type(v) retval = tdict(kt, vt) for s in S: retval[s] = v return retval def repr(self): return "tdict(kt=%s, vt=%s, data=%s)" % \ (self._kt, self._vt, super(tdict, self).__repr__()) spyne-spyne-2.14.0/spyne/util/test.py000066400000000000000000000052351417664205300175510ustar00rootroot00000000000000# encoding: utf8 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # from pprint import pformat from spyne.util import urlencode def _start_response(code, headers): print(code, pformat(headers)) def call_wsgi_app_kwargs(app, _mn='some_call', _headers=None, **kwargs): return call_wsgi_app(app, _mn, _headers, kwargs.items()) def call_wsgi_app(app, mn='some_call', headers=None, body_pairs=None): if headers is None: headers = {} if body_pairs is None: body_pairs = [] body_pairs = [(k,str(v)) for k,v in body_pairs] request = { 'QUERY_STRING': urlencode(body_pairs), 'PATH_INFO': '/%s' % mn, 'REQUEST_METHOD': 'GET', 'SERVER_NAME': 'spyne.test', 'SERVER_PORT': '0', 'wsgi.url_scheme': 'http', } print(headers) request.update(headers) out_string = [] t = None for s in app(request, _start_response): t = type(s) out_string.append(s) if t == bytes: out_string = b''.join(out_string) else: out_string = ''.join(out_string) return out_string from os import mkdir, getcwd from os.path import join, basename def show(elt, tn=None, stdout=True): if tn is None: import inspect for frame in inspect.stack(): if frame[3].startswith("test_"): cn = frame[0].f_locals['self'].__class__.__name__ tn = "%s.%s" % (cn, frame[3]) break else: raise Exception("don't be lazy and pass test name.") from lxml import html, etree out_string = etree.tostring(elt, pretty_print=True) if stdout: print(out_string) fn = '%s.html' % tn if basename(getcwd()) != 'test_html': try: mkdir('test_html') except OSError: pass f = open(join("test_html", fn), 'wb') else: f = open(fn, 'wb') f.write(html.tostring(elt, pretty_print=True, doctype="")) spyne-spyne-2.14.0/spyne/util/tlist.py000066400000000000000000000061431417664205300177300ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # adapted from: https://gist.github.com/KrzysztofCiba/4579691 ## # @file TypedList.py # @author Krzysztof.Ciba@NOSPAMgmail.com # @date 2012/07/19 08:21:22 # @brief Definition of TypedList class. class tlist(list): """ .. class:: tlist A list-like class holding only objects of specified type(s). """ def __init__(self, iterable=None, types=None): iterable = list() if not iterable else iterable # make sure it is iterable iter(iterable) types = types if isinstance(types, tuple) else (types,) for item in types: if not isinstance(item, type): raise TypeError("%s is not a type" % repr(item)) self._types = types for i in iterable: self._type_check(i) list.__init__(self, iterable) def types(self): return self._types def _type_check(self, val): if not self._types: return if not isinstance(val, self._types): raise TypeError( "Wrong type %s, this list can hold only instances of %s" % (type(val), str(self._types))) def __iadd__(self, other): map(self._type_check, other) list.__iadd__(self, other) return self def __add__(self, other): iterable = [item for item in self] + [item for item in other] return tlist(iterable, self._types) def __radd__(self, other): iterable = [item for item in other] + [item for item in self] if isinstance(other, tlist): return self.__class__(iterable, other.types()) return tlist(iterable, self._types) def __setitem__(self, key, value): itervalue = (value,) if isinstance(key, slice): iter(value) itervalue = value map(self._type_check, itervalue) list.__setitem__(self, key, value) def __setslice__(self, i, j, iterable): iter(iterable) map(self._type_check, iterable) list.__setslice__(self, i, j, iterable) def append(self, val): self._type_check(val) list.append(self, val) def extend(self, iterable): iter(iterable) map(self._type_check, iterable) list.extend(self, iterable) def insert(self, i, val): self._type_check(val) list.insert(self, i, val) spyne-spyne-2.14.0/spyne/util/toposort.py000066400000000000000000000040301417664205300204530ustar00rootroot00000000000000# http://code.activestate.com/recipes/577413-topological-sort/ # # The MIT License (MIT) # # Copyright (c) ActiveState.com # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from pprint import pformat try: # Python 3 from functools import reduce except: pass def toposort2(data): if len(data) == 0: return for k, v in data.items(): v.discard(k) # Ignore self dependencies # add items that are listed as dependencies but not as dependents to data extra_items_in_deps = reduce(set.union, data.values()) - set(data.keys()) data.update(dict([(item,set()) for item in extra_items_in_deps])) while True: ordered = set(item for item,dep in data.items() if len(dep) == 0) if len(ordered) == 0: break yield sorted(ordered, key=lambda x:repr(x)) data = dict([(item, (dep - ordered)) for item,dep in data.items() if item not in ordered]) assert not data, "A cyclic dependency exists amongst\n%s" % pformat(data) spyne-spyne-2.14.0/spyne/util/web.py000066400000000000000000000252521417664205300173500ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """ Some code dump from some time ago. If you're using this for anything serious, you're insane. """ from __future__ import absolute_import import logging logger = logging.getLogger(__name__) from inspect import isclass from spyne import rpc, Any, AnyDict, NATIVE_MAP, M, Array, ComplexModelBase, \ UnsignedInteger32, PushBase, Iterable, ModelBase, File, Service, \ ResourceNotFoundError, Unicode from spyne.const import MAX_ARRAY_ELEMENT_NUM, MAX_DICT_ELEMENT_NUM, \ MAX_STRING_FIELD_LENGTH, MAX_FIELD_NUM try: from spyne.store.relational.document import FileData from sqlalchemy.orm.exc import DetachedInstanceError except ImportError: # these are used just for isinstance checks. so we just set it to an # anonymous value FileData = type('__hidden', (object, ), {}) DetachedInstanceError = type('__hidden', (Exception, ), {}) from spyne.util import memoize, six EXCEPTION_ADDRESS = None try: from colorama.ansi import Fore from colorama.ansi import Style RED = Fore.RED + Style.BRIGHT GREEN = Fore.GREEN + Style.BRIGHT RESET = Style.RESET_ALL except ImportError: RED = "" GREEN = "" RESET = "" class ReaderService(Service): pass class WriterService(Service): pass def log_repr(obj, cls=None, given_len=None, parent=None, from_array=False, tags=None, prot=None): """Use this function if you want to serialize a ComplexModelBase instance to logs. It will: * Limit size of the String types * Limit size of Array types * Not try to iterate on iterators, push data, etc. """ if tags is None: tags = set() if obj is None: return 'None' objcls = None if hasattr(obj, '__class__'): objcls = obj.__class__ if objcls in (list, tuple): objcls = Array(Any) elif objcls is dict: objcls = AnyDict elif objcls in NATIVE_MAP: objcls = NATIVE_MAP[objcls] if objcls is not None and (cls is None or issubclass(objcls, cls)): cls = objcls cls_attrs = None logged = None if hasattr(cls, 'Attributes'): # init cls_attrs if prot is None: cls_attrs = cls.Attributes else: cls_attrs = prot.get_cls_attrs(cls) # init logged logged = cls_attrs.logged if not logged: return "%s(...)" % cls.get_type_name() if logged == '...': return "(...)" if logged == 'len': l = '?' try: if isinstance(obj, (list, tuple)): l = str(sum([len(o) for o in obj])) else: l = str(len(obj)) except (TypeError, ValueError): if given_len is not None: l = str(given_len) return "" % l if callable(logged): try: return cls_attrs.logged(obj) except Exception as e: logger.error("Exception %r in log_repr transformer ignored", e) logger.exception(e) pass if issubclass(cls, AnyDict) or isinstance(obj, dict): retval = [] if isinstance(obj, dict): if logged == 'full': for i, (k, v) in enumerate(obj.items()): retval.append('%r: %r' % (k, v)) elif logged == 'keys': for i, k in enumerate(obj.keys()): if i >= MAX_DICT_ELEMENT_NUM: retval.append("(...)") break retval.append('%r: (...)' % (k,)) elif logged == 'values': for i, v in enumerate(obj.values()): if i >= MAX_DICT_ELEMENT_NUM: retval.append("(...)") break retval.append('(...): %s' % (log_repr(v, tags=tags),)) elif logged == 'keys-full': for k in obj.keys(): retval.append('%r: (...)' % (k,)) elif logged == 'values-full': for v in obj.values(): retval.append('(...): %r' % (v,)) elif logged is True: # default behaviour for i, (k, v) in enumerate(obj.items()): if i >= MAX_DICT_ELEMENT_NUM: retval.append("(...)") break retval.append('%r: %s' % (k, log_repr(v, parent=k, tags=tags))) elif logged is None: return "(...)" else: raise ValueError("Invalid value logged=%r", logged) return "{%s}" % ', '.join(retval) else: if logged in ('full', 'keys-full', 'values-full'): retval = [repr(s) for s in obj] else: for i, v in enumerate(obj): if i >= MAX_DICT_ELEMENT_NUM: retval.append("(...)") break retval.append(log_repr(v, tags=tags)) return "[%s]" % ', '.join(retval) if ( issubclass(cls, Array) or (cls_attrs is not None and cls_attrs.max_occurs > 1) ) \ and not from_array: if id(obj) in tags: return "%s(...)" % obj.__class__.__name__ tags.add(id(obj)) retval = [] subcls = cls if issubclass(cls, Array): subcls, = cls._type_info.values() if isinstance(obj, PushBase): return '[]' if logged is None: logged = cls_attrs.logged for i, o in enumerate(obj): if logged != 'full' and i >= MAX_ARRAY_ELEMENT_NUM: retval.append("(...)") break retval.append(log_repr(o, subcls, from_array=True, tags=tags)) return "[%s]" % (', '.join(retval)) if issubclass(cls, ComplexModelBase): if id(obj) in tags: return "%s(...)" % obj.__class__.__name__ tags.add(id(obj)) retval = [] i = 0 for k, t in cls.get_flat_type_info(cls).items(): if i >= MAX_FIELD_NUM: retval.append("(...)") break if not t.Attributes.logged: continue if logged == '...': retval.append("%s=(...)" % k) continue try: v = getattr(obj, k, None) except (AttributeError, KeyError, DetachedInstanceError): v = None # HACK!: sometimes non-db attributes restored from database don't # get properly reinitialized. if isclass(v) and issubclass(v, ModelBase): continue polymap = t.Attributes.polymap if polymap is not None: t = polymap.get(v.__class__, t) if v is not None: retval.append("%s=%s" % (k, log_repr(v, t, parent=k, tags=tags))) i += 1 return "%s(%s)" % (cls.get_type_name(), ', '.join(retval)) if issubclass(cls, Unicode) and isinstance(obj, six.string_types): if len(obj) > MAX_STRING_FIELD_LENGTH: return '%r(...)' % obj[:MAX_STRING_FIELD_LENGTH] return repr(obj) if issubclass(cls, File) and isinstance(obj, File.Value): cls = obj.__class__ if issubclass(cls, File) and isinstance(obj, FileData): return log_repr(obj, FileData, tags=tags) retval = repr(obj) if len(retval) > MAX_STRING_FIELD_LENGTH: retval = retval[:MAX_STRING_FIELD_LENGTH] + "(...)" return retval def TReaderService(T, T_name): class ReaderService(ReaderService): @rpc(M(UnsignedInteger32), _returns=T, _in_message_name='get_%s' % T_name, _in_variable_names={'obj_id': "%s_id" % T_name}) def get(ctx, obj_id): return ctx.udc.session.query(T).filter_by(id=obj_id).one() @rpc(_returns=Iterable(T), _in_message_name='get_all_%s' % T_name) def get_all(ctx): return ctx.udc.session.query(T).order_by(T.id) return ReaderService def TWriterService(T, T_name, put_not_found='raise'): assert put_not_found in ('raise', 'fix') if put_not_found == 'raise': def put_not_found(obj): raise ResourceNotFoundError('%s.id=%d' % (T_name, obj.id)) elif put_not_found == 'fix': def put_not_found(obj): obj.id = None class WriterService(WriterService): @rpc(M(T), _returns=UnsignedInteger32, _in_message_name='put_%s' % T_name, _in_variable_names={'obj': T_name}) def put(ctx, obj): if obj.id is None: ctx.udc.session.add(obj) ctx.udc.session.flush() # so that we get the obj.id value else: if ctx.udc.session.query(T).get(obj.id) is None: # this is to prevent the client from setting the primary key # of a new object instead of the database's own primary-key # generator. # Instead of raising an exception, you can also choose to # ignore the primary key set by the client by silently doing # obj.id = None in order to have the database assign the # primary key the traditional way. put_not_found(obj.id) else: ctx.udc.session.merge(obj) return obj.id @rpc(M(UnsignedInteger32), _in_message_name='del_%s' % T_name, _in_variable_names={'obj_id': '%s_id' % T_name}) def del_(ctx, obj_id): count = ctx.udc.session.query(T).filter_by(id=obj_id).count() if count == 0: raise ResourceNotFoundError(obj_id) ctx.udc.session.query(T).filter_by(id=obj_id).delete() return WriterService spyne-spyne-2.14.0/spyne/util/wsgi_wrapper.py000066400000000000000000000102601417664205300212750ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """A Convenience module for wsgi wrapper routines.""" import logging logger = logging.getLogger(__name__) import os from spyne import Application from spyne.server.wsgi import WsgiApplication class WsgiMounter(object): """Simple mounter object for wsgi callables. Takes a dict where the keys are uri fragments and values are :class:`spyne.application.Application` instances. :param mounts: dict of :class:`spyne.application.Application` instances whose keys are url fragments. Use ``''`` or ``'/'`` as key to set the default handler. When a default handler is not set, an empty 404 page is returned. """ @staticmethod def default(e, s): s("404 Not found", []) return [] def __init__(self, mounts=None): self.mounts = {} for k, v in (mounts or {}).items(): if isinstance(v, Application): app = WsgiApplication(v) else: assert callable(v), "%r is not a valid wsgi app." % v app = v if k in ('', '/'): self.default = app else: self.mounts[k] = app def __call__(self, environ, start_response): path_info = environ.get('PATH_INFO', '') fragments = [a for a in path_info.split('/') if len(a) > 0] script = '' if len(fragments) > 0: script = fragments[0] app = self.mounts.get(script, self.default) if app is self.default: return app(environ, start_response) original_script_name = environ.get('SCRIPT_NAME', '') if len(script) > 0: script = "/" + script environ['SCRIPT_NAME'] = ''.join(('/', original_script_name, script)) pi = ''.join(('/', '/'.join(fragments[1:]))) if pi == '/': environ['PATH_INFO'] = '' else: environ['PATH_INFO'] = pi return app(environ, start_response) def run_twisted(apps, port, static_dir='.', interface='0.0.0.0'): """Twisted wrapper for the spyne.server.wsgi.WsgiApplication. Twisted can use one thread per request to run services, so code wrapped this way does not necessarily have to respect twisted way of doing things. :param apps: List of tuples containing (application, url) pairs :param port: Port to listen to. :param static_dir: The directory that contains static files. Pass `None` if you don't want to server static content. Url fragments in the `apps` argument take precedence. :param interface: The network interface to which the server binds, if not specified, it will accept connections on any interface by default. """ import twisted.web.server import twisted.web.static from twisted.web.resource import Resource from twisted.web.wsgi import WSGIResource from twisted.internet import reactor if static_dir != None: static_dir = os.path.abspath(static_dir) logging.info("registering static folder %r on /" % static_dir) root = twisted.web.static.File(static_dir) else: root = Resource() for app, url in apps: resource = WSGIResource(reactor, reactor, app) logging.info("registering %r on /%s" % (app, url)) root.putChild(url, resource) site = twisted.web.server.Site(root) reactor.listenTCP(port, site, interface=interface) logging.info("listening on: %s:%d" % (interface, port)) return reactor.run() spyne-spyne-2.14.0/spyne/util/xml.py000066400000000000000000000244211417664205300173700ustar00rootroot00000000000000 # # spyne - Copyright (C) Spyne contributors. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # """The `spyne.util.xml` module contains various Xml and Xml Schema related utility functions. """ from inspect import isgenerator from lxml import etree from os.path import dirname from os.path import abspath from spyne import ServiceBase, Application, srpc from spyne.context import FakeContext from spyne.interface import Interface from spyne.interface.xml_schema import XmlSchema from spyne.interface.xml_schema.parser import XmlSchemaParser, Thier_repr, PARSER from spyne.protocol import ProtocolMixin from spyne.protocol.cloth import XmlCloth from spyne.protocol.xml import XmlDocument from spyne.util.appreg import unregister_application from spyne.util.six import BytesIO from spyne.util.tlist import tlist class FakeApplication(object): def __init__(self, default_namespace): self.tns = default_namespace self.services = () self.classes = () def get_schema_documents(models, default_namespace=None): """Returns the schema documents in a dict whose keys are namespace prefixes and values are Element objects. :param models: A list of spyne.model classes that will be represented in the schema. """ if default_namespace is None: default_namespace = models[0].get_namespace() fake_app = FakeApplication(default_namespace) interface = Interface(fake_app) for m in models: m.resolve_namespace(m, default_namespace) interface.add_class(m) interface.populate_interface(fake_app) document = XmlSchema(interface) document.build_interface_document() return document.get_interface_document() def get_validation_schema(models, default_namespace=None): """Returns the validation schema object for the given models. :param models: A list of spyne.model classes that will be represented in the schema. """ if default_namespace is None: default_namespace = models[0].get_namespace() fake_app = FakeApplication(default_namespace) interface = Interface(fake_app) for m in models: m.resolve_namespace(m, default_namespace) interface.add_class(m) schema = XmlSchema(interface) schema.build_validation_schema() return schema.validation_schema def _dig(par): for elt in par: elt.tag = elt.tag.split('}')[-1] _dig(elt) _xml_object = XmlDocument() def get_object_as_xml(inst, cls=None, root_tag_name=None, no_namespace=False): """Returns an ElementTree representation of a :class:`spyne.model.complex.ComplexModel` subclass. :param inst: The instance of the class to be serialized. :param cls: The class to be serialized. Optional. :param root_tag_name: The root tag string to use. Defaults to the output of ``value.__class__.get_type_name_ns()``. :param no_namespace: When true, namespace information is discarded. """ if cls is None: cls = inst.__class__ parent = etree.Element("parent") _xml_object.to_parent(None, cls, inst, parent, cls.get_namespace(), root_tag_name) if no_namespace: _dig(parent) etree.cleanup_namespaces(parent) return parent[0] def get_object_as_xml_polymorphic(inst, cls=None, root_tag_name=None, no_namespace=False): """Returns an ElementTree representation of a :class:`spyne.model.complex.ComplexModel` subclass. :param inst: The instance of the class to be serialized. :param cls: The class to be serialized. Optional. :param root_tag_name: The root tag string to use. Defaults to the output of ``value.__class__.get_type_name_ns()``. :param no_namespace: When true, namespace information is discarded. """ if cls is None: cls = inst.__class__ if no_namespace: app = Application([ServiceBase], tns="", out_protocol=XmlDocument(polymorphic=True)) else: tns = cls.get_namespace() if tns is None: raise ValueError( "Either set a namespace for %r or pass no_namespace=True" % (cls, )) class _DummyService(ServiceBase): @srpc(cls) def f(_): pass app = Application([_DummyService], tns=tns, out_protocol=XmlDocument(polymorphic=True)) unregister_application(app) parent = etree.Element("parent", nsmap=app.interface.nsmap) app.out_protocol.to_parent(None, cls, inst, parent, cls.get_namespace(), root_tag_name) if no_namespace: _dig(parent) etree.cleanup_namespaces(parent) return parent[0] def get_xml_as_object_polymorphic(elt, cls): """Returns a native :class:`spyne.model.complex.ComplexModel` child from an ElementTree representation of the same class. :param elt: The xml document to be deserialized. :param cls: The class the xml document represents. """ tns = cls.get_namespace() if tns is None: raise ValueError("Please set a namespace for %r" % (cls, )) class _DummyService(ServiceBase): @srpc(cls) def f(_): pass app = Application([_DummyService], tns=tns, in_protocol=XmlDocument(polymorphic=True)) unregister_application(app) return app.in_protocol.from_element(FakeContext(app=app), cls, elt) def get_object_as_xml_cloth(inst, cls=None, no_namespace=False, encoding='utf8'): """Returns an ElementTree representation of a :class:`spyne.model.complex.ComplexModel` subclass. :param inst: The instance of the class to be serialized. :param cls: The class to be serialized. Optional. :param root_tag_name: The root tag string to use. Defaults to the output of ``value.__class__.get_type_name_ns()``. :param no_namespace: When true, namespace information is discarded. """ if cls is None: cls = inst.__class__ if cls.get_namespace() is None and no_namespace is None: no_namespace = True if no_namespace is None: no_namespace = False ostr = BytesIO() xml_cloth = XmlCloth(use_ns=(not no_namespace)) ctx = FakeContext() with etree.xmlfile(ostr, encoding=encoding) as xf: ctx.outprot_ctx.doctype_written = False ctx.protocol.prot_stack = tlist([], ProtocolMixin) tn = cls.get_type_name() ret = xml_cloth.subserialize(ctx, cls, inst, xf, tn) assert not isgenerator(ret) return ostr.getvalue() def get_xml_as_object(elt, cls): """Returns a native :class:`spyne.model.complex.ComplexModel` child from an ElementTree representation of the same class. :param elt: The xml document to be deserialized. :param cls: The class the xml document represents. """ return _xml_object.from_element(None, cls, elt) def parse_schema_string(s, files={}, repr_=Thier_repr(with_ns=False), skip_errors=False): """Parses a schema string and returns a _Schema object. :param s: The string or bytes object that contains the schema document. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ elt = etree.fromstring(s, parser=PARSER) return XmlSchemaParser(files, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) def parse_schema_element(elt, files={}, repr_=Thier_repr(with_ns=False), skip_errors=False): """Parses a `` element and returns a _Schema object. :param elt: The `` element, an lxml.etree._Element instance. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ return XmlSchemaParser(files, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) def parse_schema_file(file_name, files=None, repr_=Thier_repr(with_ns=False), skip_errors=False): """Parses a schema file and returns a _Schema object. Schema files typically have the `*.xsd` extension. :param file_name: The path to the file that contains the schema document to be parsed. :param files: A dict that maps namespaces to path to schema files that contain the schema document for those namespaces. :param repr_: A callable that functions as `repr`. :param skip_errors: Skip parsing errors and return a partial schema. See debug log for details. :return: :class:`spyne.interface.xml_schema.parser._Schema` instance. """ if files is None: files = dict() elt = etree.fromstring(open(file_name, 'rb').read(), parser=PARSER) wd = abspath(dirname(file_name)) return XmlSchemaParser(files, wd, repr_=repr_, skip_errors=skip_errors).parse_schema(elt) spyne-spyne-2.14.0/tox.ini000066400000000000000000000070251417664205300154170ustar00rootroot00000000000000# this is solely for running django tests. # be sure to clear coverage.xml before every test run. this is normally done by # python setup.py test [tox] envlist = py27-dj111, py35-dj111, py35-dj22, py36-dj111, py36-dj22, py36-dj30, py37-dj111, py37-dj22, py37-dj30, py38-dj22, py38-dj30, usedevelop = True [testenv] setenv = PYTHONHASHSEED = 3332349646 commands = py.test --junitxml=test_result.{envname}.xml --cov-append --cov-report= --cov spyne --tb=short {env:TESTS} # # DJANGO 1.11 # [testenv:py27-dj111] basepython = {env:BASEPYTHON:python2.7} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=1.11,<1.12 -rrequirements/test_django_req.txt [testenv:py35-dj111] basepython = {env:BASEPYTHON:python3.5} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=1.11,<1.12 -rrequirements/test_django_req.txt [testenv:py36-dj111] basepython = {env:BASEPYTHON:python3.6} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=1.11,<1.12 -rrequirements/test_django_req.txt [testenv:py37-dj111] basepython = {env:BASEPYTHON:python3.7} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=1.11.17,<1.12 -rrequirements/test_django_req.txt # # DJANGO 2.2 # [testenv:py35-dj22] basepython = {env:BASEPYTHON:python3.5} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=2.2,<2.3 -rrequirements/test_django_req.txt [testenv:py36-dj22] basepython = {env:BASEPYTHON:python3.6} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=2.2,<2.3 -rrequirements/test_django_req.txt [testenv:py37-dj22] basepython = {env:BASEPYTHON:python3.7} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=2.2,<2.3 -rrequirements/test_django_req.txt [testenv:py38-dj22] basepython = {env:BASEPYTHON:python3.8} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=2.2.8,<2.3 -rrequirements/test_django_req.txt # # DJANGO 3.0 # [testenv:py36-dj30] basepython = {env:BASEPYTHON:python3.6} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=3.0,<3.1 -rrequirements/test_django_req.txt [testenv:py37-dj30] basepython = {env:BASEPYTHON:python3.7} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=3.0,<3.1 -rrequirements/test_django_req.txt [testenv:py38-dj30] basepython = {env:BASEPYTHON:python3.8} setenv = DJANGO_SETTINGS_MODULE=rpctest.settings PYTHONPATH = {toxinidir}/examples/django/ TESTS = spyne/test/interop/test_django.py deps = Django>=3.0,<3.1 -rrequirements/test_django_req.txt