├── .bumpversion.cfg ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dco.yml └── workflows │ ├── build.yml │ ├── lint.yml │ └── release.yml ├── .gitignore ├── .readthedocs.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── contributors.txt ├── docs ├── Makefile ├── _static │ └── .gitkeep ├── api.rst ├── conf.py ├── examples.rst ├── index.rst ├── requirements.txt └── tracing.rst ├── pyproject.toml ├── renovate.json ├── samples ├── README ├── config.py.sample ├── icmd5250_dspsyssts.py ├── icmd_rtvjoba.py ├── ipase_ps.py ├── ipgm_bad_trace_db2.py ├── ipgm_bad_trace_file.py ├── ipgm_bad_trace_rest.py ├── ipgm_bad_trace_terminal.py ├── ipgm_zzcall.py ├── isleep_rest_async.py ├── isql_callproc.py ├── isql_prepare.py ├── isql_query.py ├── isql_rest_async.py ├── isrvpgm_qgyrhrl.py ├── isrvpgm_qgyrhrl_hole.py ├── isrvpgm_zzarray.py ├── istop_msg_qsysopr_before_pgm_call.py ├── ixml_diag.py └── ixml_zzvary.py ├── setup.cfg ├── src └── itoolkit │ ├── README │ ├── __init__.py │ ├── base.py │ ├── command.py │ ├── db2 │ ├── __init__.py │ └── idb2call.py │ ├── doc │ └── README │ ├── errors.py │ ├── lib │ ├── __init__.py │ └── ilibcall.py │ ├── program_call.py │ ├── rest │ ├── __init__.py │ └── irestcall.py │ ├── shell.py │ ├── sql.py │ ├── toolkit.py │ └── transport │ ├── __init__.py │ ├── _direct.py │ ├── base.py │ ├── database.py │ ├── direct.py │ ├── errors.py │ ├── http.py │ └── ssh.py └── tests ├── conftest.py ├── test_cmd_error.py ├── test_cmd_rexx.py ├── test_deprecated_transports.py ├── test_pgm.py ├── test_pgm_error.py ├── test_srvpgm.py ├── test_unit_cmd.py ├── test_unit_cmd5250.py ├── test_unit_data.py ├── test_unit_idb2call.py ├── test_unit_irestcall.py ├── test_unit_pgm.py ├── test_unit_sh.py ├── test_unit_sql_execute.py ├── test_unit_sql_fetch.py ├── test_unit_sql_free.py ├── test_unit_sql_parm.py ├── test_unit_sql_prepare.py ├── test_unit_sql_query.py ├── test_unit_srvpgm.py ├── test_unit_transport_database.py ├── test_unit_transport_direct.py ├── test_unit_transport_http.py └── test_unit_transport_ssh.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 2.0.0-dev 3 | commit = True 4 | message = chore: Bump version: {current_version} → {new_version} 5 | tag = True 6 | tag_name = {new_version} 7 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? 8 | serialize = 9 | {major}.{minor}.{patch}-{release} 10 | {major}.{minor}.{patch} 11 | 12 | [bumpversion:file:pyproject.toml] 13 | search = version = "{current_version}" 14 | replace = version = "{new_version}" 15 | 16 | [bumpversion:file:src/itoolkit/__init__.py] 17 | 18 | [bumpversion:file:docs/conf.py] 19 | 20 | [bumpversion:part:release] 21 | optional_value = gamma 22 | values = 23 | dev 24 | gamma 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug Report" 3 | about: Did you find a bug? 4 | title: '' 5 | labels: bug 6 | assignees: 'kadler' 7 | 8 | --- 9 | 10 | 18 | 19 | 23 | * **itoolkit version**: 24 | * **Python version**: 25 | * **OS Name and Version**: 26 | * **IBM i version**: 27 | * **XMLSERVICE version**: 28 | 29 | **Describe the bug** 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F381 Feature Request" 3 | about: Suggest an idea for new features or improvement! 4 | title: '' 5 | labels: enhancement 6 | assignees: 'kadler' 7 | 8 | --- 9 | 10 | 18 | 19 | **What is the use case for this enhancement? ie. What problem are you trying to solve?** 20 | 21 | 22 | **Describe the solution you'd like** 23 | 28 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 4 16 | fail-fast: false 17 | matrix: 18 | python-version: 19 | - '3.13' 20 | - '3.12' 21 | - '3.11' 22 | - '3.10' 23 | - '3.9' 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | - name: Install Poetry 32 | run: | 33 | curl -sSL https://install.python-poetry.org | python - -y --version 1.8.5 34 | - name: Install dependencies 35 | run: | 36 | poetry config virtualenvs.create false 37 | poetry self add virtualenv@20.30.0 # https://github.com/python-poetry/poetry/issues/10378#issuecomment-2858311161 38 | poetry install 39 | - name: Run tests 40 | run: python -m pytest tests 41 | - name: Report coverage 42 | env: 43 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 44 | run: python -m coveralls 45 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint PR 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: '3.x' 17 | - name: Install dependencies 18 | run: | 19 | pip install flake8 20 | - name: Lint with flake8 21 | run: | 22 | # stop the build if there are Python syntax errors or undefined names 23 | python -m flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics 24 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 25 | python -m flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | Release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.x" 20 | 21 | - name: Install Poetry 22 | run: | 23 | curl -sSL https://install.python-poetry.org | python - -y --version 1.1.15 24 | - name: Update PATH 25 | run: echo "$HOME/.local/bin" >> $GITHUB_PATH 26 | 27 | - name: Build project for distribution 28 | run: poetry build 29 | 30 | - name: Create Release 31 | uses: ncipollo/release-action@v1 32 | with: 33 | artifacts: "dist/*" 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | draft: false 36 | 37 | - name: Publish to PyPI 38 | env: 39 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 40 | run: poetry publish 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | __pycache__ 4 | *.py[cod] 5 | *.egg-info 6 | docs/_build 7 | .eggs 8 | *.so 9 | poetry.lock 10 | .coverage 11 | coverage.xml 12 | .vscode 13 | .venv/ 14 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: "ubuntu-22.04" 10 | tools: 11 | python: "3.9" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # Optionally set the version of Python and requirements required to build your docs 18 | python: 19 | install: 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Contributing In General 4 | 5 | Our project welcomes external contributions. If you have an itch, please feel 6 | free to scratch it. 7 | 8 | To contribute code or documentation, please submit a [pull request](https://github.com/IBM/python-itoolkit/pulls). 9 | 10 | A good way to familiarize yourself with the codebase and contribution process is 11 | to look for and tackle low-hanging fruit in the [issue tracker](https://github.com/IBM/python-itoolkit/issues). 12 | These will be marked with the [good first issue](https://github.com/IBM/python-itoolkit/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label. You may also want to look at those marked with [help wanted](https://github.com/IBM/python-itoolkit/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). 13 | 14 | **Note: We appreciate your effort, and want to avoid a situation where a contribution 15 | requires extensive rework (by you or by us), sits in backlog for a long time, or 16 | cannot be accepted at all!** 17 | 18 | ### Proposing new features 19 | 20 | If you would like to implement a new feature, please [raise an issue](https://github.com/IBM/python-itoolkit/issues) 21 | before sending a pull request so the feature can be discussed. This is to avoid 22 | you wasting your valuable time working on a feature that the project developers 23 | are not interested in accepting into the code base. 24 | 25 | ### Fixing bugs 26 | 27 | If you would like to fix a bug, please [raise an issue](https://github.com/IBM/python-itoolkit/issues) before sending a 28 | pull request so it can be tracked. 29 | 30 | ## Legal 31 | 32 | We have tried to make it as easy as possible to make contributions. This 33 | applies to how we handle the legal aspects of contribution. We use the 34 | same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://github.com/hyperledger/fabric/blob/master/docs/source/DCO1.1.txt) - that the Linux® Kernel [community](https://elinux.org/Developer_Certificate_Of_Origin) 35 | uses to manage code contributions. 36 | 37 | We simply ask that when submitting a patch for review, the developer 38 | must include a sign-off statement in the commit message. 39 | 40 | Here is an example Signed-off-by line, which indicates that the 41 | submitter accepts the DCO: 42 | 43 | ```text 44 | Signed-off-by: John Doe 45 | ``` 46 | 47 | You can include this automatically when you commit a change to your 48 | local git repository using the following command: 49 | 50 | ```bash 51 | git commit -s 52 | ``` 53 | 54 | ## Communication 55 | 56 | Please feel free to connect with us on our [Ryver forum](https://ibmioss.ryver.com/index.html#forums/1000128). You can join the Ryver community [here](https://ibmioss.ryver.com/application/signup/members/9tJsXDG7_iSSi1Q). 57 | 58 | ## Setup 59 | 60 | This project follows the standard packaging way: 61 | 62 | ```bash 63 | # Install requirements 64 | python -m pip install -r requirements.txt 65 | 66 | # Build 67 | python setup.py build 68 | 69 | # Install 70 | python setup.py install 71 | ``` 72 | 73 | ## Testing 74 | 75 | This package uses [pytest](https://docs.pytest.org/en/latest/) for its tests. 76 | You can run them like so: 77 | 78 | ```bash 79 | # Test installed package (python setup.py install) 80 | python -m pytest tests 81 | 82 | # Test local changes which haven't been installed 83 | PYTHONPATH=src python -m pytest tests 84 | ``` 85 | 86 | ## Coding style guidelines 87 | 88 | This project will attempt to follow [PEP 8](https://www.python.org/dev/peps/pep-0008) guidelines. New contributions should enforce PEP-8 style and we welcome any changes (see issue [#19](https://github.com/IBM/python-itoolkit/issues/19) for more details) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 International Business Machines 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python XMLSERVICE Toolkit 2 | ========================= 3 | 4 | [![Build Status](https://github.com/IBM/python-itoolkit/actions/workflows/build.yml/badge.svg?branch=main&event=push)](https://github.com/IBM/python-itoolkit/actions/workflows/build.yml) 5 | [![Latest version released on PyPi](https://img.shields.io/pypi/v/itoolkit.svg)](https://pypi.python.org/pypi/itoolkit) 6 | [![Python Version Support](https://img.shields.io/pypi/pyversions/itoolkit.svg)](https://pypi.org/project/itoolkit/) 7 | [![Documentation Status](https://readthedocs.org/projects/python-itoolkit/badge/?version=latest)](https://python-itoolkit.readthedocs.io/en/latest/?badge=latest) 8 | 9 | 10 | itoolkit is a Python interface to the 11 | [XMLSERVICE](https://github.com/IBM/xmlservice) toolkit for the 12 | [IBM i](https://en.wikipedia.org/wiki/IBM_i) platform. 13 | 14 | ```python 15 | from itoolkit import * 16 | from itoolkit.transport import DatabaseTransport 17 | import pyodbc 18 | 19 | conn = pyodbc.connect("DSN=*LOCAL") 20 | itransport = DatabaseTransport(conn) 21 | itool = iToolKit() 22 | 23 | itool.add(iCmd5250('wrkactjob', 'WRKACTJOB')) 24 | itool.call(itransport) 25 | wrkactjob = itool.dict_out('wrkactjob') 26 | 27 | print(wrkactjob) 28 | ``` 29 | 30 | For more, check out the [samples](https://python-itoolkit.readthedocs.io/en/latest/examples.html). 31 | 32 | Feature Support 33 | --------------- 34 | 35 | - Call ILE programs & service programs 36 | - Call CL Commands 37 | - Call PASE shell commands 38 | 39 | Documentation 40 | ------------- 41 | 42 | The docs can be found at 43 | 44 | Installation 45 | ------------ 46 | 47 | You can install itoolkit simply using `pip`: 48 | 49 | ```bash 50 | python -m pip install itoolkit 51 | ``` 52 | 53 | Tests 54 | ----- 55 | 56 | To test the installed itoolkit 57 | 58 | ```bash 59 | python -m pytest 60 | ``` 61 | 62 | To test the local code: 63 | 64 | ```bash 65 | PYTHONPATH=src python -m pytest 66 | ``` 67 | 68 | Contributing 69 | ------------ 70 | 71 | Please read the [contribution guidelines](https://github.com/IBM/python-itoolkit/blob/main/CONTRIBUTING.md). 72 | 73 | Releasing a New Version 74 | ----------------------- 75 | 76 | Run the following commands 77 | 78 | ``` 79 | # checkout and pull the latest code from master 80 | git checkout master 81 | git pull 82 | 83 | # bump to a release version (a tag and commit are made) 84 | bumpversion release 85 | 86 | # build the new distribution and upload to PyPI 87 | poetry publish --build 88 | 89 | # bump to the new dev version (a commit is made) 90 | bumpversion --no-tag patch 91 | 92 | # push the new tag and commits 93 | git push origin master --tags 94 | ``` 95 | 96 | License 97 | ------- 98 | 99 | MIT - See [LICENSE](https://github.com/IBM/python-itoolkit/blob/main/LICENSE) 100 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | This file contains the names of those who have committed to this 2 | project (or at least those who've added their name here to be 3 | recognized). If you make a change, please add your name here. 4 | 5 | Original Author and Author of XMLSERVICE: 6 | Tony Cairns 7 | 8 | Additional Changes and Improvements 9 | Kevin Adler 10 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = itoolkit 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/python-itoolkit/1c44e561b663c2ae36a2045d33bf378f8a025d13/docs/_static/.gitkeep -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API Reference 4 | ============= 5 | 6 | .. module:: itoolkit 7 | 8 | 9 | Toolkit Object 10 | -------------- 11 | 12 | .. autoclass:: iToolKit 13 | :members: 14 | :inherited-members: 15 | 16 | Toolkit Operations 17 | ------------------ 18 | 19 | .. autoclass:: iPgm 20 | :members: 21 | :inherited-members: 22 | 23 | .. autoclass:: iSrvPgm 24 | :members: 25 | :inherited-members: 26 | 27 | .. autoclass:: iCmd 28 | :members: 29 | :inherited-members: 30 | 31 | .. autoclass:: iCmd5250 32 | :members: 33 | :inherited-members: 34 | 35 | .. autoclass:: iSh 36 | :members: 37 | :inherited-members: 38 | 39 | .. autoclass:: iXml 40 | :members: 41 | :inherited-members: 42 | 43 | .. autoclass:: iDS 44 | :members: 45 | :inherited-members: 46 | 47 | .. autoclass:: iData 48 | :members: 49 | :inherited-members: 50 | 51 | 52 | Transports 53 | ---------- 54 | .. module:: itoolkit.transport 55 | 56 | .. autoclass:: XmlServiceTransport 57 | :members: 58 | :inherited-members: 59 | 60 | HTTP Transport 61 | ~~~~~~~~~~~~~~ 62 | 63 | .. autoclass:: HttpTransport 64 | :members: 65 | 66 | Database Transport 67 | ~~~~~~~~~~~~~~~~~~ 68 | 69 | .. autoclass:: DatabaseTransport 70 | :members: 71 | 72 | SSH Transport 73 | ~~~~~~~~~~~~~~~~~~ 74 | 75 | .. autoclass:: SshTransport 76 | :members: 77 | 78 | 79 | Direct Memory Transport 80 | ~~~~~~~~~~~~~~~~~~~~~~~ 81 | 82 | .. autoclass:: DirectTransport 83 | :members: 84 | .. note:: 85 | This transport will only work when run on an IBM i system. On other operating 86 | systems, calling it will fail with a :class:`RuntimeError`. 87 | .. warning:: 88 | When using a 64-bit Python, this transport will only work with XMLSERVICE 89 | 2.0.1 or higher. When using the system XMLSERVICE in QXMLSERV, the following 90 | PTFs are available to fix this problem: 91 | 92 | - IBM i 7.4 - SI70669 93 | - IBM i 7.3 - SI70668 94 | - IBM i 7.2 - SI70667 95 | 96 | 97 | Exceptions 98 | ---------- 99 | .. module:: itoolkit 100 | :noindex: 101 | 102 | .. autoclass:: TransportError 103 | :members: 104 | 105 | 106 | .. module:: itoolkit.transport 107 | :noindex: 108 | 109 | .. autoclass:: TransportClosedError 110 | :members: 111 | 112 | 113 | .. module:: itoolkit 114 | :noindex: 115 | 116 | .. autoclass:: TransportClosedException 117 | :members: 118 | 119 | 120 | Deprecated Transports 121 | --------------------- 122 | 123 | .. module:: itoolkit.rest.irestcall 124 | .. py:class:: iRestCall 125 | .. deprecated:: 1.6.0 126 | Use :class:`itoolkit.transport.HttpTransport` instead. 127 | 128 | .. module:: itoolkit.db2.idb2call 129 | .. py:class:: iDB2Call 130 | .. deprecated:: 1.6.0 131 | Use :class:`itoolkit.transport.DatabaseTransport` instead. 132 | 133 | .. module:: itoolkit.lib.ilibcall 134 | .. py:class:: iLibCall 135 | .. deprecated:: 1.6.0 136 | Use :class:`itoolkit.transport.DirectTransport` instead. 137 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # itoolkit documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jan 9 23:16:08 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('../src')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.githubpages', 36 | 'sphinx.ext.autodoc', 37 | 'sphinx.ext.napoleon', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'itoolkit' 54 | copyright = '2018, Tony Cairns' 55 | author = 'Tony Cairns' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '2.0.0-dev' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '2.0.0-dev' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = 'en' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.venv'] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = False 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = 'sphinx_rtd_theme' 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | 103 | 104 | # -- Options for HTMLHelp output ------------------------------------------ 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = 'itoolkitdoc' 108 | 109 | 110 | # -- Options for LaTeX output --------------------------------------------- 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'itoolkit.tex', 'itoolkit Documentation', 135 | 'Tony Cairns', 'manual'), 136 | ] 137 | 138 | 139 | # -- Options for manual page output --------------------------------------- 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | (master_doc, 'itoolkit', 'itoolkit Documentation', 145 | [author], 1) 146 | ] 147 | 148 | 149 | # -- Options for Texinfo output ------------------------------------------- 150 | 151 | # Grouping the document tree into Texinfo files. List of tuples 152 | # (source start file, target name, title, author, 153 | # dir menu entry, description, category) 154 | texinfo_documents = [ 155 | (master_doc, 'itoolkit', 'itoolkit Documentation', 156 | author, 'itoolkit', 'One line description of project.', 157 | 'Miscellaneous'), 158 | ] 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ======== 5 | 6 | Calling the DSPSYSSTS CL Command and Displaying Output 7 | ------------------------------------------------------ 8 | 9 | .. include:: ../samples/icmd5250_dspsyssts.py 10 | :code: 11 | 12 | Calling the RTVJOBA CL Command and Getting Output Parameters 13 | ------------------------------------------------------------ 14 | 15 | .. include:: ../samples/icmd_rtvjoba.py 16 | :code: 17 | 18 | Calling the PASE ps Command and Getting Output 19 | ---------------------------------------------- 20 | 21 | .. include:: ../samples/ipase_ps.py 22 | :code: 23 | 24 | Tracing to the Terminal 25 | ----------------------- 26 | 27 | .. include:: ../samples/ipgm_bad_trace_terminal.py 28 | :code: 29 | 30 | Tracing to a File 31 | ----------------- 32 | 33 | .. include:: ../samples/ipgm_bad_trace_file.py 34 | :code: 35 | 36 | Calling an RPG Program 37 | ---------------------- 38 | 39 | .. include:: ../samples/ipgm_zzcall.py 40 | :code: 41 | 42 | .. .. include:: ../samples/isleep_rest_async.py 43 | .. :code: 44 | 45 | .. .. include:: ../samples/isql_callproc.py 46 | .. :code: 47 | 48 | .. .. include:: ../samples/isql_prepare.py 49 | .. :code: 50 | 51 | .. .. include:: ../samples/isql_query.py 52 | .. :code: 53 | 54 | .. .. include:: ../samples/isql_rest_async.py 55 | .. :code: 56 | 57 | Calling a Service Program with "Hole" Parameter 58 | ----------------------------------------------- 59 | 60 | .. include:: ../samples/isrvpgm_qgyrhrl_hole.py 61 | :code: 62 | 63 | Calling a Service Program 64 | ------------------------- 65 | 66 | .. include:: ../samples/isrvpgm_qgyrhrl.py 67 | :code: 68 | 69 | Calling a Service Program With an Array Parameter 70 | ------------------------------------------------- 71 | 72 | .. include:: ../samples/isrvpgm_zzarray.py 73 | :code: 74 | 75 | Using \*debug to Cause XMLSERVICE to Enter a Message Wait 76 | --------------------------------------------------------- 77 | 78 | .. include:: ../samples/istop_msg_qsysopr_before_pgm_call.py 79 | :code: 80 | 81 | Using iXml to Get XMLSERVICE Diagnostics 82 | ---------------------------------------- 83 | 84 | .. include:: ../samples/ixml_diag.py 85 | :code: 86 | 87 | Using iXml to Call a Program with a Varchar Parameter 88 | ----------------------------------------------------- 89 | 90 | .. include:: ../samples/ixml_zzvary.py 91 | :code: 92 | 93 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | itoolkit for Python 2 | =================== 3 | 4 | itoolkit is a Python interface to the `XMLSERVICE`_ toolkit for the `IBM i`_ platform. 5 | 6 | .. _XMLSERVICE: https://github.com/IBM/xmlservice 7 | .. _IBM i: https://en.wikipedia.org/wiki/IBM_i 8 | 9 | Usage 10 | ----- 11 | 12 | .. code-block:: python 13 | 14 | from itoolkit import * 15 | from itoolkit.transport import DatabaseTransport 16 | import ibm_db_dbi 17 | 18 | conn = ibm_db_dbi.connect() 19 | itransport = DatabaseTransport(conn) 20 | itool = iToolKit() 21 | 22 | itool.add(iCmd5250('wrkactjob', 'WRKACTJOB')) 23 | itool.call(itransport) 24 | wrkactjob = itool.dict_out('wrkactjob') 25 | 26 | print(wrkactjob) 27 | 28 | For more, check out the :ref:`Examples `. 29 | 30 | Supported Python Versions 31 | ------------------------- 32 | 33 | python-itoolkit currently supports Python 2.7 and Python 3.4+. 34 | 35 | .. warning:: 36 | python-itoolkit 1.x will be the last series to support Python 2.7, 3.4, and 37 | 3.5. These versions will no longer be supported in python-itoolkit 2.0. 38 | 39 | Feature Support 40 | --------------- 41 | 42 | - Call ILE programs & service programs 43 | - Call CL Commands 44 | - Call PASE shell commands 45 | 46 | Installation 47 | ------------ 48 | 49 | You can install itoolkit simply using `pip`: 50 | 51 | .. code-block:: bash 52 | 53 | python -m pip install itoolkit 54 | 55 | Table of Contents 56 | ----------------- 57 | 58 | .. toctree:: 59 | :maxdepth: 1 60 | 61 | api 62 | tracing 63 | examples 64 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==7.2.6 2 | sphinx-rtd-theme==1.3.0 3 | -------------------------------------------------------------------------------- /docs/tracing.rst: -------------------------------------------------------------------------------- 1 | .. _tracing: 2 | 3 | Tracing 4 | ======= 5 | 6 | When diagnosing itoolkit issues, it may be helpful to trace the call to XMLSERVICE. 7 | To facilitate this, :func:`iToolKit.call()`, will trace out the following info: 8 | 9 | - XMLSERVICE ipc and ctl options and any transport-specific options 10 | - The generated input XML before sending to XMLSERVICE 11 | - The output XML received from XMLSERVICE 12 | 13 | If the received XML fails to parse, it will also be dumped in hex. 14 | 15 | 16 | Capturing Trace Output 17 | ---------------------- 18 | 19 | Since itoolkit version 2.0, tracing data is logged via the standard Python 20 | `logging `_ framework using the 21 | ``itoolkit-trace`` logger and all trace records are logged at ``INFO`` level. 22 | 23 | There are many ways to capture the logs, though a standard way is to write them 24 | to a file. For more ways to capture the logs, refer to `Python's built-in 25 | loggers `_. 26 | 27 | 28 | Basic File Trace Example 29 | ~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | This is a simple example which outputs the trace messages to a file with no 32 | other metadata. 33 | 34 | .. code-block:: python 35 | 36 | import logging 37 | logger = logging.getLogger('itoolkit-trace') 38 | logger.setLevel(logging.INFO) 39 | logger.addHandler(logging.FileHandler('itoolkit.log')) 40 | 41 | tk.call(transport) 42 | 43 | Advanced File Trace Example 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | In this example, each trace message is preceded by the time it was logged along 47 | with the process id and thread id where it was logged. This is more useful in 48 | multi-threaded Python server applications. 49 | 50 | .. code-block:: python 51 | 52 | import logging 53 | 54 | # Log the time, process id, and thread id along with the message 55 | formatter = logging.Formatter(fmt='%(asctime)s %(process)d %(thread)d %(message)s') 56 | 57 | handler = logging.FileHandler('itoolkit.log') 58 | handler.setFormatter(formatter) 59 | 60 | logger = logging.getLogger('itoolkit-trace') 61 | logger.setLevel(logging.INFO) 62 | logger.addHandler(handler) 63 | 64 | tk.call(transport) 65 | 66 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "itoolkit" 3 | version = "2.0.0-dev" 4 | description = "IBM i XMLSERVICE toolkit for Python" 5 | readme = "README.md" 6 | homepage = "https://github.com/IBM/python-itoolkit" 7 | repository = "https://github.com/IBM/python-itoolkit" 8 | documentation = "https://python-itoolkit.readthedocs.io" 9 | license = "MIT" 10 | authors = [ 11 | "Korinne Adler ", 12 | "Tony Cairns ", 13 | ] 14 | packages = [ 15 | { include = "itoolkit", from = "src" }, 16 | ] 17 | include = [ 18 | { path = "tests/*", format = "sdist" }, 19 | { path = "samples/*", format = "sdist" }, 20 | { path = "docs/*", format = "sdist" }, 21 | { path = "setup.cfg", format = "sdist" }, 22 | { path = "CONTRIBUTING.md", format = "sdist" }, 23 | { path = "contributors.txt", format = "sdist" }, 24 | ] 25 | classifiers = [ 26 | "Development Status :: 5 - Production/Stable", 27 | "Intended Audience :: Developers", 28 | "Topic :: Software Development :: Libraries :: Python Modules", 29 | "Topic :: System", 30 | ] 31 | 32 | [tool.poetry.dependencies] 33 | python = ">=3.9.0,<3.14.0" 34 | 35 | 36 | [tool.poetry.dev-dependencies] 37 | pytest = [ 38 | { version = ">=7.2.1", python = ">=3.11" }, 39 | { version = ">=6.2.5", python = ">=3.10, <3.11" }, 40 | { version = ">=5.0.0", python = "<3.10" }, 41 | ] 42 | pytest-mock = [ 43 | { version = ">=3.7.0", python = ">=3.7" }, 44 | ] 45 | coverage = [ 46 | { version = ">=6.3.0", python = ">=3.7" }, 47 | ] 48 | coveralls = [ 49 | { version = ">=3.3.0", python = ">=3.5" }, 50 | ] 51 | pytest-cov = "~2.12.0" 52 | flake8 = ">=3.6.0" 53 | mock = "^3.0.5" 54 | bumpversion = "^0.5.0" 55 | sphinx = ">=1.8.4" 56 | sphinx-rtd-theme = "^0.4.0" 57 | 58 | [build-system] 59 | requires = ["poetry-core>=1.0.0a6"] 60 | build-backend = "poetry.core.masonry.api" 61 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /samples/README: -------------------------------------------------------------------------------- 1 | configuration: 2 | > edit config.py 3 | run: 4 | > python sample.py 5 | 6 | -------------------------------------------------------------------------------- /samples/config.py.sample: -------------------------------------------------------------------------------- 1 | """ 2 | Configure: 3 | Requires XMSLERVICE library installed, see following link installation 4 | http://yips.idevcloud.com/wiki/index.php/XMLService/XMLSERVICE 5 | 6 | Transports: 7 | 1) XMLSERVICE direct call (current job) 8 | from itoolkit.transport import DirectTransport 9 | itransport = DirectTransport() 10 | 11 | 2) XMLSERVICE db2 call 12 | from itoolkit.transport import DatabaseTransport 13 | 14 | # Connect to any PEP-249 compliant driver which can call XMLSERVICE stored procedures 15 | # eg. ibm_db_dbi 16 | import ibm_db_dbi 17 | conn = ibm_db_dbi.connect() 18 | 19 | # eg. or PyODBC 20 | import pyodbc 21 | conn = pyodbc.connect('DSN=mydsn;UID=...') 22 | 23 | itransport = DatabaseTransport(conn) 24 | 25 | 3) XMLSERVICE http/rest/web call (Apache job) 26 | from itoolkit.transport import HttpTransport 27 | itransport = HttpTransport(url, user, password) 28 | 29 | 4) XMLService ssh call 30 | import paramiko 31 | ssh = paramiko.SSHClient() 32 | ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) 33 | ssh.connect("hostname", username="username", password="password") 34 | itransport = SshTransport(ssh) 35 | """ 36 | from itoolkit.transport import DirectTransport 37 | itransport = DirectTransport() 38 | # itransport = DirectTransport("*here *cdata *debug") # i will stop, inquiry message qsysopr 39 | 40 | -------------------------------------------------------------------------------- /samples/icmd5250_dspsyssts.py: -------------------------------------------------------------------------------- 1 | # Bottom 2 | # Type command, press Enter. 3 | # ===> dspsyssts 4 | # Display System Status LP0364D 5 | # 06/22/15 15:22:28 6 | # % CPU used . . . . . . . : .1 Auxiliary storage: 7 | # Elapsed time . . . . . . : 00:00:01 System ASP . . . . . . : 176.2 G 8 | # Jobs in system . . . . . : 428 % system ASP used . . : 75.6481 9 | import config 10 | from itoolkit import * 11 | 12 | itool = iToolKit() 13 | itool.add(iCmd5250('dspsyssts', 'dspsyssts')) 14 | 15 | # xmlservice 16 | itool.call(config.itransport) 17 | 18 | # output 19 | dspsyssts = itool.dict_out('dspsyssts') 20 | if 'error' in dspsyssts: 21 | print (dspsyssts['error']) 22 | exit() 23 | else: 24 | print (dspsyssts['dspsyssts']) 25 | 26 | -------------------------------------------------------------------------------- /samples/icmd_rtvjoba.py: -------------------------------------------------------------------------------- 1 | # RTVJOBA can't issue from command line, 2 | # but works with itoolkit 3 | import config 4 | from itoolkit import * 5 | 6 | # modify iToolKit not include row node 7 | itool = iToolKit(iparm=0, iret=0, ids=1, irow=0) 8 | itool.add(iCmd('rtvjoba', 'RTVJOBA USRLIBL(?) SYSLIBL(?) CCSID(?N) OUTQ(?)')) 9 | 10 | # xmlservice 11 | itool.call(config.itransport) 12 | 13 | # output 14 | rtvjoba = itool.dict_out('rtvjoba') 15 | print (rtvjoba) 16 | if 'error' in rtvjoba: 17 | print (rtvjoba['error']) 18 | exit() 19 | else: 20 | print('USRLIBL = ' + rtvjoba['USRLIBL']) 21 | print('SYSLIBL = ' + rtvjoba['SYSLIBL']) 22 | print('CCSID = ' + rtvjoba['CCSID']) 23 | print('OUTQ = ' + rtvjoba['OUTQ']) 24 | 25 | -------------------------------------------------------------------------------- /samples/ipase_ps.py: -------------------------------------------------------------------------------- 1 | # > ps -ef 2 | # UID PID PPID C STIME TTY TIME CMD 3 | # qsecofr 12 11 0 May 08 - 8:33 /QOpenSys/QIBM/ProdData/JavaVM/jdk60/32bit/jre/lib/ppc/jvmStartPase 566 4 | # qtmhhttp 31 1 0 May 08 - 0:00 /usr/local/zendsvr/bin/watchdog -c /usr/local/zendsvr/etc/watchdog-monitor.ini -s monitor 5 | import config 6 | from itoolkit import * 7 | 8 | itool = iToolKit() 9 | itool.add(iSh('ps', 'ps -ef')) 10 | 11 | # xmlservice 12 | itool.call(config.itransport) 13 | 14 | # output 15 | ps = itool.dict_out('ps') 16 | if 'error' in ps: 17 | print (ps['error']) 18 | exit() 19 | else: 20 | print (ps['ps']) 21 | 22 | -------------------------------------------------------------------------------- /samples/ipgm_bad_trace_db2.py: -------------------------------------------------------------------------------- 1 | from itoolkit import * 2 | from itoolkit.transport import DatabaseTransport 3 | import ibm_db_dbi 4 | 5 | conn = ibm_db_dbi.connect() 6 | itransport = DatabaseTransport(conn) 7 | 8 | itool = iToolKit() 9 | itool.add( 10 | iPgm('zzcall','ZZCALLNOT') 11 | .addParm(iData('INCHARA','1a','a')) 12 | ) 13 | 14 | # xmlservice write trace log to *terminal 15 | itool.trace_open() 16 | itool.call(itransport) 17 | itool.trace_close() 18 | 19 | zzcall = itool.dict_out('zzcall') 20 | if 'success' in zzcall: 21 | print (zzcall['success']) 22 | else: 23 | print (zzcall['error']) 24 | exit() 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/ipgm_bad_trace_file.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | itool = iToolKit() 4 | itool.add( 5 | iPgm('zzcall','ZZCALLNOT') 6 | .addParm(iData('INCHARA','1a','a')) 7 | ) 8 | 9 | # xmlservice write trace log to /tmp/python_toolkit_(tonyfile).log 10 | itool.trace_open('tonyfile') 11 | itool.call(config.itransport) 12 | itool.trace_close() 13 | 14 | zzcall = itool.dict_out('zzcall') 15 | if 'success' in zzcall: 16 | print (zzcall['success']) 17 | else: 18 | print (zzcall['error']) 19 | exit() 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/ipgm_bad_trace_rest.py: -------------------------------------------------------------------------------- 1 | from itoolkit.transport import HttpTransport 2 | from itoolkit import * 3 | 4 | itransport = HttpTransport('http://yips.idevcloud.com/cgi-bin/xmlcgi.pgm','*NONE','*NONE') 5 | 6 | itool = iToolKit() 7 | itool.add( 8 | iPgm('zzcall','ZZCALLNOT') 9 | .addParm(iData('INCHARA','1a','a')) 10 | ) 11 | 12 | # xmlservice write trace log to *terminal 13 | itool.trace_open() 14 | itool.call(itransport) 15 | itool.trace_close() 16 | 17 | zzcall = itool.dict_out('zzcall') 18 | if 'success' in zzcall: 19 | print (zzcall['success']) 20 | else: 21 | print (zzcall['error']) 22 | exit() 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/ipgm_bad_trace_terminal.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | itool = iToolKit() 4 | itool.add( 5 | iPgm('zzcall','ZZCALLNOT') 6 | .addParm(iData('INCHARA','1a','a')) 7 | ) 8 | 9 | # xmlservice write trace log to *terminal 10 | itool.trace_open() 11 | itool.call(config.itransport) 12 | itool.trace_close() 13 | 14 | zzcall = itool.dict_out('zzcall') 15 | if 'success' in zzcall: 16 | print (zzcall['success']) 17 | else: 18 | print (zzcall['error']) 19 | exit() 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/ipgm_zzcall.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | # XMLSERVICE/ZZCALL: 4 | # D INCHARA S 1a 5 | # D INCHARB S 1a 6 | # D INDEC1 S 7p 4 7 | # D INDEC2 S 12p 2 8 | # D INDS1 DS 9 | # D DSCHARA 1a 10 | # D DSCHARB 1a 11 | # D DSDEC1 7p 4 12 | # D DSDEC2 12p 2 13 | # *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 14 | # * main(): Control flow 15 | # *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 16 | # C *Entry PLIST 17 | # C PARM INCHARA 18 | # C PARM INCHARB 19 | # C PARM INDEC1 20 | # C PARM INDEC2 21 | # C PARM INDS1 22 | itool = iToolKit() 23 | itool.add(iCmd('chglibl', 'CHGLIBL LIBL(XMLSERVICE)')) 24 | itool.add( 25 | iPgm('zzcall','ZZCALL') 26 | .addParm(iData('INCHARA','1a','a')) 27 | .addParm(iData('INCHARB','1a','b')) 28 | .addParm(iData('INDEC1','7p4','32.1234')) 29 | .addParm(iData('INDEC2','12p2','33.33')) 30 | .addParm( 31 | iDS('INDS1') 32 | .addData(iData('DSCHARA','1a','a')) 33 | .addData(iData('DSCHARB','1a','b')) 34 | .addData(iData('DSDEC1','7p4','32.1234')) 35 | .addData(iData('DSDEC2','12p2','33.33')) 36 | ) 37 | ) 38 | 39 | # xmlservice 40 | itool.call(config.itransport) 41 | 42 | # output 43 | chglibl = itool.dict_out('chglibl') 44 | if 'success' in chglibl: 45 | print (chglibl['success']) 46 | else: 47 | print (chglibl['error']) 48 | exit() 49 | 50 | zzcall = itool.dict_out('zzcall') 51 | if 'success' in zzcall: 52 | print (zzcall['success']) 53 | print (" INCHARA : " + zzcall['INCHARA']) 54 | print (" INCHARB : " + zzcall['INCHARB']) 55 | print (" INDEC1 : " + zzcall['INDEC1']) 56 | print (" INDEC2 : " + zzcall['INDEC2']) 57 | print (" INDS1.DSCHARA: " + zzcall['INDS1']['DSCHARA']) 58 | print (" INDS1.DSCHARB: " + zzcall['INDS1']['DSCHARB']) 59 | print (" INDS1.DSDEC1 : " + zzcall['INDS1']['DSDEC1']) 60 | print (" INDS1.DSDEC2 : " + zzcall['INDS1']['DSDEC2']) 61 | else: 62 | print (zzcall['error']) 63 | exit() 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/isleep_rest_async.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import threading 3 | import urllib 4 | from itoolkit.transport import HttpTransport 5 | from itoolkit import * 6 | class iShSleep(): 7 | def __init__(self, icmd): 8 | self.itran = HttpTransport('http://yips.idevcloud.com/cgi-bin/xmlcgi.pgm','*NONE','*NONE') 9 | self.itool = iToolKit() 10 | self.itool.add(iSh('igo',icmd)) 11 | def go(self): 12 | self.itool.call(self.itran) 13 | return self.itool.dict_out('igo') 14 | def get_url(q, icmd): 15 | q.put(iShSleep(icmd).go()) 16 | theshs = ["echo 'thing 1==>';date;sleep 10;date", 17 | "echo 'thing 2==>';date;sleep 5;date"] 18 | q = queue.Queue() 19 | for u in theshs: 20 | t = threading.Thread(target=get_url, args = (q,u)) 21 | t.daemon = True 22 | t.start() 23 | # q.join() 24 | for u in theshs: 25 | s = q.get() 26 | print(s) 27 | 28 | -------------------------------------------------------------------------------- /samples/isql_callproc.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | 4 | itool = iToolKit(iparm=1) 5 | sql = "DROP PROCEDURE XMLSERVICE/FLUBBER\n" 6 | itool.add(iSqlQuery('crt', sql)) 7 | itool.add(iSqlFree('free0')) 8 | sql = "CREATE PROCEDURE XMLSERVICE/FLUBBER(IN first_name VARCHAR(128), INOUT any_name VARCHAR(128))\n" 9 | sql += "LANGUAGE SQL\n" 10 | sql += "BEGIN\n" 11 | sql += "SET any_name = 'Flubber';\n" 12 | sql += "END\n" 13 | itool.add(iSqlQuery('crt', sql)) 14 | itool.add(iSqlFree('free1')) 15 | itool.add(iSqlPrepare('callflubber', "call XMLSERVICE/FLUBBER(?,?)")) 16 | itool.add( 17 | iSqlExecute('exec') 18 | .addParm(iSqlParm('myin','Jones')) 19 | .addParm(iSqlParm('myout','jjjjjjjjjjjjjjjjjjjjjuuuuuuuuuuuuuuuunnnnnnnnnkkkkkkkkkkkk')) 20 | ) 21 | itool.add(iSqlFree('free2')) 22 | # print(itool.xml_in()) 23 | # exit() 24 | 25 | # xmlservice 26 | itool.call(config.itransport) 27 | # print(itool.xml_out()) 28 | 29 | # output 30 | FLUBBER = itool.dict_out('exec') 31 | if 'error' in FLUBBER: 32 | print (FLUBBER['error']) 33 | exit() 34 | else: 35 | print ('myout = ' + FLUBBER['myout']['data']) 36 | 37 | -------------------------------------------------------------------------------- /samples/isql_prepare.py: -------------------------------------------------------------------------------- 1 | # strsql 2 | # 3 | # ===> select * from QIWS/QCUSTCDT where LSTNAM='Jones' or LSTNAM='Vine' 4 | # 5 | # Display Data 6 | # Data width . . . . . . : 102 7 | # Position to line . . . . . Shift to column . . . . . . 8 | # ....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+.... 9 | # CUSNUM LSTNAM INIT STREET CITY STATE ZIPCOD CDTLMT CHGCO 10 | # 839,283 Jones B D 21B NW 135 St Clay NY 13,041 400 1 11 | # 392,859 Vine S S PO Box 79 Broton VT 5,046 700 1 12 | # ******** End of data ******** 13 | import config 14 | from itoolkit import * 15 | 16 | itool = iToolKit() 17 | itool.add(iSqlPrepare('cust2prep', "select * from QIWS/QCUSTCDT where LSTNAM=? or LSTNAM=?")) 18 | itool.add( 19 | iSqlExecute('cust2exec') 20 | .addParm(iSqlParm('pm1','Jones')) 21 | .addParm(iSqlParm('pm2','Vine')) 22 | ) 23 | itool.add(iSqlFetch('cust2fetch')) 24 | itool.add(iSqlFree('cust2free')) 25 | 26 | # xmlservice 27 | itool.call(config.itransport) 28 | 29 | # output 30 | QCUSTCDT = itool.dict_out('cust2fetch') 31 | # print(QCUSTCDT) 32 | if 'error' in QCUSTCDT: 33 | print (QCUSTCDT['error']) 34 | exit() 35 | else: 36 | for row in QCUSTCDT['row']: 37 | print('row:') 38 | print(' CDTDUE :' + row['CDTDUE']) 39 | print(' CDTLMT :' + row['CDTLMT']) 40 | print(' CUSNUM :' + row['CUSNUM']) 41 | print(' CHGCOD :' + row['CHGCOD']) 42 | print(' STREET :' + row['STREET']) 43 | print(' INIT :' + row['INIT']) 44 | print(' BALDUE :' + row['BALDUE']) 45 | print(' LSTNAM :' + row['LSTNAM']) 46 | print(' ZIPCOD :' + row['ZIPCOD']) 47 | print(' CITY :' + row['CITY']) 48 | print(' STATE :' + row['STATE']) 49 | 50 | -------------------------------------------------------------------------------- /samples/isql_query.py: -------------------------------------------------------------------------------- 1 | # strsql 2 | # 3 | # ===> select * from QIWS/QCUSTCDT where LSTNAM='Jones' or LSTNAM='Vine' 4 | # 5 | # Display Data 6 | # Data width . . . . . . : 102 7 | # Position to line . . . . . Shift to column . . . . . . 8 | # ....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+.... 9 | # CUSNUM LSTNAM INIT STREET CITY STATE ZIPCOD CDTLMT CHGCO 10 | # 839,283 Jones B D 21B NW 135 St Clay NY 13,041 400 1 11 | # 392,859 Vine S S PO Box 79 Broton VT 5,046 700 1 12 | # ******** End of data ******** 13 | import config 14 | from itoolkit import * 15 | 16 | itool = iToolKit() 17 | itool.add(iSqlQuery('custquery', "select * from QIWS/QCUSTCDT where LSTNAM='Jones' or LSTNAM='Vine'")) 18 | itool.add(iSqlFetch('custfetch')) 19 | itool.add(iSqlFree('custfree')) 20 | 21 | # xmlservice 22 | itool.call(config.itransport) 23 | 24 | # output 25 | QCUSTCDT = itool.dict_out('custfetch') 26 | # print(QCUSTCDT) 27 | if 'error' in QCUSTCDT: 28 | print (QCUSTCDT['error']) 29 | exit() 30 | else: 31 | for row in QCUSTCDT['row']: 32 | print('row:') 33 | print(' CDTDUE :' + row['CDTDUE']) 34 | print(' CDTLMT :' + row['CDTLMT']) 35 | print(' CUSNUM :' + row['CUSNUM']) 36 | print(' CHGCOD :' + row['CHGCOD']) 37 | print(' STREET :' + row['STREET']) 38 | print(' INIT :' + row['INIT']) 39 | print(' BALDUE :' + row['BALDUE']) 40 | print(' LSTNAM :' + row['LSTNAM']) 41 | print(' ZIPCOD :' + row['ZIPCOD']) 42 | print(' CITY :' + row['CITY']) 43 | print(' STATE :' + row['STATE']) 44 | 45 | -------------------------------------------------------------------------------- /samples/isql_rest_async.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import threading 3 | import urllib 4 | from itoolkit.transport import HttpTransport 5 | from itoolkit import * 6 | class iDB2Async(): 7 | def __init__(self, isql): 8 | self.itran = HttpTransport('http://yips.idevcloud.com/cgi-bin/xmlcgi.pgm','*NONE','*NONE') 9 | self.itool = iToolKit() 10 | self.itool.add(iSqlQuery('iqry', isql)) 11 | self.itool.add(iSqlFetch('ifch')) 12 | self.itool.add(iSqlFree('ifre')) 13 | def go(self): 14 | self.itool.call(self.itran) 15 | return self.itool.dict_out('ifch') 16 | def get_url(q, icmd): 17 | q.put(iDB2Async(icmd).go()) 18 | thedb2s = ["select CUSNUM from QIWS/QCUSTCDT where LSTNAM='Jones'", 19 | "select CUSNUM from QIWS/QCUSTCDT where LSTNAM='Johnson'"] 20 | q = queue.Queue() 21 | for u in thedb2s: 22 | t = threading.Thread(target=get_url, args = (q,u)) 23 | t.daemon = True 24 | t.start() 25 | # q.join() 26 | for u in thedb2s: 27 | s = q.get() 28 | print(s) 29 | 30 | -------------------------------------------------------------------------------- /samples/isrvpgm_qgyrhrl.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | # Retrieve Hardware Resource List (QGYRHRL, QgyRtvHdwRscList) API 4 | # Service Program: QGYRHR 5 | # Default Public Authority: *USE 6 | # Threadsafe: No 7 | # Required Parameter Group: 8 | # Output Char(*)..............Receiver variable (RHRL0100, RHRL0110) 9 | # Input Binary(4).............Length of receiver variable 10 | # Input Char(8)...............Format name 11 | # Input Binary(4).............Resource category (see hardware resource category) 12 | # I/O Char(*).................Error code 13 | # RHRL0100 Format 14 | # BINARY(4)...................Bytes returned 15 | # BINARY(4)...................Bytes available 16 | # BINARY(4)...................Number of resources returned 17 | # BINARY(4)...................Length of resource entry 18 | # CHAR(*).....................Resource entries 19 | # These fields repeat for each resource. 20 | # BINARY(4)...................Resource category 21 | # BINARY(4)...................Family level 22 | # BINARY(4)...................Line type 23 | # CHAR(10)....................Resource name 24 | # CHAR(4).....................Type number 25 | # CHAR(3).....................Model number 26 | # CHAR(1).....................Status 27 | # CHAR(8).....................System to which adapter is connected 28 | # CHAR(12)....................Adapter address 29 | # CHAR(50)....................Description 30 | # CHAR(24)....................Resource kind (liar, liar, pants on fire ... binary, not char) 31 | # hardware resource category: 32 | # 1 All hardware resources (does not include local area network resources) 33 | # 2 Communication resources 34 | # 3 Local work station resources 35 | # 4 Processor resources 36 | # 5 Storage device resources 37 | # 6 Coupled system adapter resources 38 | # 7 Local area network resources 39 | # 8 Cryptographic resources 40 | # 9 Tape and optical resources 41 | # 10 Tape resources 42 | # 11 Optical resources 43 | itool = iToolKit() 44 | itool.add( 45 | iSrvPgm('qgyrhr','QGYRHR','QgyRtvHdwRscList') 46 | .addParm( 47 | iDS('RHRL0100_t',{'len':'rhrlen'}) 48 | .addData(iData('rhrRet','10i0','')) 49 | .addData(iData('rhrAvl','10i0','')) 50 | .addData(iData('rhrNbr','10i0','',{'enddo':'mycnt'})) 51 | .addData(iData('rhrLen','10i0','')) 52 | .addData(iDS('res_t',{'dim':'999','dou':'mycnt'}) 53 | .addData(iData('resCat','10i0','')) 54 | .addData(iData('resLvl','10i0','')) 55 | .addData(iData('resLin','10i0','')) 56 | .addData(iData('resNam','10a','')) 57 | .addData(iData('resTyp','4a','')) 58 | .addData(iData('resMod','3a','')) 59 | .addData(iData('resSts','1a','')) 60 | .addData(iData('resSys','8a','')) 61 | .addData(iData('resAdp','12a','')) 62 | .addData(iData('resDsc','50a','')) 63 | .addData(iData('resKnd','24b','')) 64 | ) 65 | ) 66 | .addParm(iData('rcvlen','10i0','',{'setlen':'rhrlen'})) 67 | .addParm(iData('fmtnam','10a','RHRL0100')) 68 | .addParm(iData('rescat','10i0','3')) # 3 Local work station resources 69 | .addParm( 70 | iDS('ERRC0100_t',{'len':'errlen'}) 71 | .addData(iData('errRet','10i0','')) 72 | .addData(iData('errAvl','10i0','')) 73 | .addData(iData('errExp','7A','',{'setlen':'errlen'})) 74 | .addData(iData('errRsv','1A','')) 75 | ) 76 | ) 77 | # xmlservice 78 | itool.call(config.itransport) 79 | #output 80 | qgyrhr = itool.dict_out('qgyrhr') 81 | if 'success' in qgyrhr: 82 | print (qgyrhr['success']) 83 | print (" Length of receiver variable......" + qgyrhr['rcvlen']) 84 | print (" Format name......................" + qgyrhr['fmtnam']) 85 | print (" Resource category................" + qgyrhr['rescat']) 86 | RHRL0100_t = qgyrhr['RHRL0100_t'] 87 | print (' RHRL0100_t:') 88 | print (" Bytes returned................." + RHRL0100_t['rhrRet']) 89 | print (" Bytes available................" + RHRL0100_t['rhrAvl']) 90 | print (" Number of resources returned..." + RHRL0100_t['rhrNbr']) 91 | print (" Length of resource entry......." + RHRL0100_t['rhrLen']) 92 | if int(RHRL0100_t['rhrNbr']) > 0: 93 | res_t = RHRL0100_t['res_t'] 94 | for rec in res_t: 95 | print (" --------------------------------------------------------") 96 | keys = rec.keys() 97 | print (" Resource category............" + rec['resCat']) 98 | print (" Family level................." + rec['resLvl']) 99 | print (" Line type...................." + rec['resLin']) 100 | print (" Resource name................" + rec['resNam']) 101 | print (" Type number.................." + rec['resTyp']) 102 | print (" Model number................." + rec['resMod']) 103 | print (" Status......................." + rec['resSts']) 104 | print (" System adapter connected....." + rec['resSys']) 105 | print (" Adapter address.............." + rec['resAdp']) 106 | print (" Description.................." + rec['resDsc']) 107 | print (" Resource kind................" + rec['resKnd']) 108 | else: 109 | print (qgyrhr['error']) 110 | exit() 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /samples/isrvpgm_qgyrhrl_hole.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | # Retrieve Hardware Resource List (QGYRHRL, QgyRtvHdwRscList) API 4 | # Service Program: QGYRHR 5 | # Default Public Authority: *USE 6 | # Threadsafe: No 7 | # Required Parameter Group: 8 | # Output Char(*)..............Receiver variable (RHRL0100, RHRL0110) 9 | # Input Binary(4).............Length of receiver variable 10 | # Input Char(8)...............Format name 11 | # Input Binary(4).............Resource category (see hardware resource category) 12 | # I/O Char(*).................Error code 13 | # RHRL0100 Format 14 | # BINARY(4)...................Bytes returned 15 | # BINARY(4)...................Bytes available 16 | # BINARY(4)...................Number of resources returned 17 | # BINARY(4)...................Length of resource entry 18 | # CHAR(*).....................Resource entries 19 | # These fields repeat for each resource. 20 | # BINARY(4)...................Resource category 21 | # BINARY(4)...................Family level 22 | # BINARY(4)...................Line type 23 | # CHAR(10)....................Resource name 24 | # CHAR(4).....................Type number 25 | # CHAR(3).....................Model number 26 | # CHAR(1).....................Status 27 | # CHAR(8).....................System to which adapter is connected 28 | # CHAR(12)....................Adapter address 29 | # CHAR(50)....................Description 30 | # CHAR(24)....................Resource kind (liar, liar, pants on fire ... binary, not char) 31 | # hardware resource category: 32 | # 1 All hardware resources (does not include local area network resources) 33 | # 2 Communication resources 34 | # 3 Local work station resources 35 | # 4 Processor resources 36 | # 5 Storage device resources 37 | # 6 Coupled system adapter resources 38 | # 7 Local area network resources 39 | # 8 Cryptographic resources 40 | # 9 Tape and optical resources 41 | # 10 Tape resources 42 | # 11 Optical resources 43 | itool = iToolKit() 44 | itool.add( 45 | iSrvPgm('qgyrhr','QGYRHR','QgyRtvHdwRscList') 46 | .addParm( 47 | iDS('RHRL0100_t',{'len':'rhrlen'}) 48 | .addData(iData('rhrRet','10i0','')) 49 | .addData(iData('rhrAvl','10i0','')) 50 | .addData(iData('rhrNbr','10i0','',{'enddo':'mycnt'})) 51 | .addData(iData('rhrLen','10i0','')) 52 | .addData(iDS('res_t',{'dim':'999','dou':'mycnt'}) 53 | .addData(iData('resCat','10i0','')) 54 | .addData(iData('resLvl','10i0','')) 55 | .addData(iData('resLin','10i0','')) 56 | .addData(iData('resNam','10a','')) 57 | .addData(iData('resTyp','4a','')) 58 | .addData(iData('resMod','3a','')) 59 | .addData(iData('resSts','1a','')) 60 | .addData(iData('resSys','8a','')) 61 | .addData(iData('resAdp','12a','')) 62 | .addData(iData('resDsc','50h','')) # was 50a 63 | .addData(iData('resKnd','24h','')) # was 24b 64 | ) 65 | ) 66 | .addParm(iData('rcvlen','10i0','',{'setlen':'rhrlen'})) 67 | .addParm(iData('fmtnam','10a','RHRL0100')) 68 | .addParm(iData('rescat','10i0','3')) # 3 Local work station resources 69 | .addParm( 70 | iDS('ERRC0100_t',{'len':'errlen'}) 71 | .addData(iData('errRet','10i0','')) 72 | .addData(iData('errAvl','10i0','')) 73 | .addData(iData('errExp','7A','',{'setlen':'errlen'})) 74 | .addData(iData('errRsv','1A','')) 75 | ) 76 | ) 77 | # xmlservice 78 | itool.call(config.itransport) 79 | #output 80 | qgyrhr = itool.dict_out('qgyrhr') 81 | if 'success' in qgyrhr: 82 | print (qgyrhr['success']) 83 | print (" Length of receiver variable......" + qgyrhr['rcvlen']) 84 | print (" Format name......................" + qgyrhr['fmtnam']) 85 | print (" Resource category................" + qgyrhr['rescat']) 86 | RHRL0100_t = qgyrhr['RHRL0100_t'] 87 | print (' RHRL0100_t:') 88 | print (" Bytes returned................." + RHRL0100_t['rhrRet']) 89 | print (" Bytes available................" + RHRL0100_t['rhrAvl']) 90 | print (" Number of resources returned..." + RHRL0100_t['rhrNbr']) 91 | print (" Length of resource entry......." + RHRL0100_t['rhrLen']) 92 | if int(RHRL0100_t['rhrNbr']) > 0: 93 | res_t = RHRL0100_t['res_t'] 94 | for rec in res_t: 95 | print (" --------------------------------------------------------") 96 | keys = rec.keys() 97 | print (" Resource category............" + rec['resCat']) 98 | print (" Family level................." + rec['resLvl']) 99 | print (" Line type...................." + rec['resLin']) 100 | print (" Resource name................" + rec['resNam']) 101 | print (" Type number.................." + rec['resTyp']) 102 | print (" Model number................." + rec['resMod']) 103 | print (" Status......................." + rec['resSts']) 104 | print (" System adapter connected....." + rec['resSys']) 105 | print (" Adapter address.............." + rec['resAdp']) 106 | print (" Description.................." + rec['resDsc']) 107 | print (" Resource kind................" + rec['resKnd']) 108 | else: 109 | print (qgyrhr['error']) 110 | exit() 111 | 112 | -------------------------------------------------------------------------------- /samples/isrvpgm_zzarray.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | # D ARRAYMAX c const(999) 4 | # D dcRec_t ds qualified based(Template) 5 | # D dcMyName 10A 6 | # D dcMyJob 4096A 7 | # D dcMyRank 10i 0 8 | # D dcMyPay 12p 2 9 | # *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | # * zzarray: check return array aggregate 11 | # *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 12 | # P zzarray B export 13 | # D zzarray PI likeds(dcRec_t) dim(ARRAYMAX) 14 | # D myName 10A 15 | # D myMax 10i 0 16 | # D myCount 10i 0 17 | itool = iToolKit() 18 | itool.add(iCmd('chglibl', 'CHGLIBL LIBL(XMLSERVICE)')) 19 | itool.add( 20 | iSrvPgm('zzarray','ZZSRV','ZZARRAY') 21 | .addParm(iData('myName','10a','ranger')) 22 | .addParm(iData('myMax','10i0','8')) 23 | .addParm(iData('myCount','10i0','',{'enddo':'mycnt'})) 24 | .addRet( 25 | iDS('dcRec_t',{'dim':'999','dou':'mycnt'}) 26 | .addData(iData('dcMyName','10a','')) 27 | .addData(iData('dcMyJob','4096a','')) 28 | .addData(iData('dcMyRank','10i0','')) 29 | .addData(iData('dcMyPay','12p2','')) 30 | ) 31 | ) 32 | 33 | # xmlservice 34 | itool.call(config.itransport) 35 | 36 | # output 37 | # print(itool.xml_out()) 38 | chglibl = itool.dict_out('chglibl') 39 | if 'success' in chglibl: 40 | print (chglibl['success']) 41 | else: 42 | print (chglibl['error']) 43 | exit() 44 | 45 | zzarray = itool.dict_out('zzarray') 46 | # print(zzarray) 47 | if 'success' in zzarray: 48 | print (zzarray['success']) 49 | print (" myName : " + zzarray['myName']) 50 | print (" myMax : " + zzarray['myMax']) 51 | print (" myCount : " + zzarray['myCount']) 52 | dcRec_t = zzarray['dcRec_t'] 53 | for rec in dcRec_t: 54 | print (' dcRec_t:') 55 | print (" dcMyName : " + rec['dcMyName']) 56 | print (" dcMyJob : " + rec['dcMyJob']) 57 | print (" dcMyRank : " + rec['dcMyRank']) 58 | print (" dcMyPay : " + rec['dcMyPay']) 59 | else: 60 | print (zzarray['error']) 61 | exit() 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/istop_msg_qsysopr_before_pgm_call.py: -------------------------------------------------------------------------------- 1 | from itoolkit import * 2 | from itoolkit.transport import DirectTransport 3 | 4 | print("********************") 5 | print("********************") 6 | print("Hey user,") 7 | print("Using '*debug' transport parameter allows debug halt before run.") 8 | print ("\n itransport = DirectTransport('*here *debug')\n") 9 | print("Expect qsysopr inquire message, you must answer to continue script.") 10 | print("You may attach a debugger before you answer the inquiry.") 11 | print("\n dspmsg qsysopr\n") 12 | print(" Reply inquiry message any character.") 13 | print(" From . . . : ADC 06/25/15 14:08:07") 14 | print(" Debug client 362262/QSECOFR/QP0ZSPWP") 15 | print(" Reply . . : c\n") 16 | print("Script continues to run after answer (call PGM, etc.)") 17 | print("********************") 18 | print("********************") 19 | 20 | itransport = DirectTransport("*here *debug") # i will stop, inquiry message qsysopr 21 | itool = iToolKit() 22 | itool.add(iCmd('chglibl', 'CHGLIBL LIBL(XMLSERVICE)')) 23 | itool.add( 24 | iPgm('zzcall','ZZCALL') 25 | .addParm(iData('INCHARA','1a','a')) 26 | .addParm(iData('INCHARB','1a','b')) 27 | .addParm(iData('INDEC1','7p4','32.1234')) 28 | .addParm(iData('INDEC2','12p2','33.33')) 29 | .addParm( 30 | iDS('INDS1') 31 | .addData(iData('DSCHARA','1a','a')) 32 | .addData(iData('DSCHARB','1a','b')) 33 | .addData(iData('DSDEC1','7p4','32.1234')) 34 | .addData(iData('DSDEC2','12p2','33.33')) 35 | ) 36 | ) 37 | 38 | # xmlservice 39 | itool.call(itransport) 40 | 41 | # output 42 | chglibl = itool.dict_out('chglibl') 43 | if 'success' in chglibl: 44 | print (chglibl['success']) 45 | else: 46 | print (chglibl['error']) 47 | exit() 48 | 49 | zzcall = itool.dict_out('zzcall') 50 | if 'success' in zzcall: 51 | print (zzcall['success']) 52 | print (" INCHARA : " + zzcall['INCHARA']) 53 | print (" INCHARB : " + zzcall['INCHARB']) 54 | print (" INDEC1 : " + zzcall['INDEC1']) 55 | print (" INDEC2 : " + zzcall['INDEC2']) 56 | print (" INDS1.DSCHARA: " + zzcall['INDS1']['DSCHARA']) 57 | print (" INDS1.DSCHARB: " + zzcall['INDS1']['DSCHARB']) 58 | print (" INDS1.DSDEC1 : " + zzcall['INDS1']['DSDEC1']) 59 | print (" INDS1.DSDEC2 : " + zzcall['INDS1']['DSDEC2']) 60 | else: 61 | print (zzcall['error']) 62 | exit() 63 | 64 | 65 | -------------------------------------------------------------------------------- /samples/ixml_diag.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | 4 | # from itoolkit.transport import DirectTransport 5 | # itransport = DirectTransport("*here *debug") # i will stop, inquiry message qsysopr 6 | 7 | itool = iToolKit() 8 | itool.add(iCmd('chglibl2', 'CHGLIBL LIBL(QTEMP XMLSERVICE)')) 9 | itool.add(iCmd('chglibl3', 'CHGLIBL LIBL(SOMEBAD42)')) 10 | myxml = "" 11 | itool.add(iXml(myxml)) 12 | 13 | print(itool.xml_in()) 14 | 15 | 16 | # xmlservice 17 | itool.call(config.itransport) 18 | # itool.call(itransport) 19 | 20 | # output 21 | print(itool.xml_out()) 22 | diag = itool.dict_out() 23 | if 'version' in diag: 24 | print ("version : "+diag['version']) 25 | print ("job : "+diag['jobnbr']+'/'+diag['jobuser']+'/'+diag['jobname']) 26 | print ("jobipc : "+diag['jobipc']) 27 | print ("curuser : "+diag['curuser']) 28 | print ("ccsid : "+diag['ccsid']) 29 | print ("dftccsid : "+diag['dftccsid']) 30 | print ("paseccsid : "+diag['paseccsid']) 31 | print ("syslibl : "+diag['syslibl']) 32 | print ("usrlibl : "+diag['usrlibl']) 33 | joblog = diag['joblog'].replace("\n"," ") 34 | cpflist = "" 35 | for word in joblog.split(' '): 36 | if word[:3] == 'CPF' or word[:3] == 'MCH': 37 | cpflist += word + " " 38 | if diag['jobcpf'] == "": 39 | diag['jobcpf'] = word 40 | print ("jobcpf : "+diag['jobcpf'] + " ( " + cpflist + ")") 41 | print ("joblog :\n" + diag['joblog']) 42 | 43 | -------------------------------------------------------------------------------- /samples/ixml_zzvary.py: -------------------------------------------------------------------------------- 1 | import config 2 | from itoolkit import * 3 | # XMLSERVICE/ZZSRV.ZZVARY: 4 | # P zzvary B export 5 | # D zzvary PI 20A varying 6 | # D myName 10A varying 7 | itool = iToolKit() 8 | itool.add(iXml("CHGLIBL LIBL(XMLSERVICE)")) 9 | myxml = "" 10 | myxml += "" 11 | myxml += "]]>" 12 | myxml += "" 13 | myxml += "" 14 | myxml += "]]>" 15 | myxml += "" 16 | myxml += "" 17 | itool.add(iXml(myxml)) 18 | 19 | # xmlservice 20 | itool.call(config.itransport) 21 | 22 | # output 23 | chglibl = itool.dict_out('chglibl') 24 | if 'success' in chglibl: 25 | print (chglibl['success']) 26 | else: 27 | print (chglibl['error']) 28 | exit() 29 | 30 | zzvary = itool.dict_out('zzvary') 31 | if 'success' in zzvary: 32 | print (zzvary['success']) 33 | # print (" myName : " + zzvary['myName']) ... input only, no output 34 | print (" myNameis : " + zzvary['myNameis']) 35 | else: 36 | print (zzvary['error']) 37 | exit() 38 | 39 | 40 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | 4 | [tool:pytest] 5 | testpaths = 6 | tests 7 | addopts = 8 | --cov=itoolkit 9 | -r a 10 | -------------------------------------------------------------------------------- /src/itoolkit/README: -------------------------------------------------------------------------------- 1 | 2 | ************************ 3 | XMLSERVICE Toolkit (see doc/itoolkit.txt) 4 | ************************ 5 | Module itoolkit 6 | --------------- 7 | This module XMLSERVICE toolkit interface. 8 | 9 | License: 10 | BSD (LICENSE) 11 | -- or -- 12 | http://yips.idevcloud.com/wiki/index.php/XMLService/LicenseXMLService 13 | 14 | Configure (**** IMPORTANT ****): 15 | Requires XMLSERVICE library installed, see following link installation 16 | http://yips.idevcloud.com/wiki/index.php/XMLService/XMLSERVICE 17 | (use crtxml for XMLSERVICE library) 18 | 19 | Install: 20 | > export PATH=/QOpenSys/python/bin:$PATH 21 | > export LIBPATH=/QOpenSys/python/lib 22 | > easy_install itoolkit-1.0-py3.4.egg 23 | 24 | Environment variables: 25 | IBM i library for XMLSERVICE 26 | > export XMLSERVICE=XMLSERVICE 27 | 28 | Transport classes: 29 | class DirectTransport: Transport XMLSERVICE direct job call (within job/process calls). 30 | class DatabaseTransport: Transport XMLSERVICE calls over database connection. 31 | class HttpTransport: Transport XMLSERVICE calls over standard HTTP rest. 32 | class SshTransport: Transport XMLSERVICE calls over SSH. 33 | 34 | XMLSERVICE classes: 35 | Base: 36 | class iToolKit: Main iToolKit XMLSERVICE collector and output parser. 37 | class iBase: IBM i XMLSERVICE call addable operation(s). 38 | 39 | *CMDs: 40 | class iCmd(iBase): IBM i XMLSERVICE call *CMD not returning *OUTPUT. 41 | 42 | PASE: 43 | class iSh(iBase): IBM i XMLSERVICE call 5250 *CMD returning *OUTPUT. 44 | class iCmd5250(iSh): IBM i XMLSERVICE call PASE utilities. 45 | 46 | *PGM or *SRVPGM: 47 | class iPgm (iBase): IBM i XMLSERVICE call *PGM. 48 | class iSrvPgm (iPgm): IBM i XMLSERVICE call *SRVPGM. 49 | class iParm (iBase): Parameter child node for iPgm or iSrvPgm 50 | class iRet (iBase): Return structure child node for iSrvPgm 51 | class iDS (iBase): Data structure child node for iPgm, iSrvPgm, 52 | or nested iDS data structures. 53 | class iData (iBase): Data value child node for iPgm, iSrvPgm, 54 | or iDS data structures. 55 | 56 | DB2: 57 | class iSqlQuery (iBase): IBM i XMLSERVICE call DB2 execute direct SQL statment. 58 | class iSqlPrepare (iBase): IBM i XMLSERVICE call DB2 prepare SQL statment. 59 | class iSqlExecute (iBase): IBM i XMLSERVICE call execute a DB2 prepare SQL statment. 60 | class iSqlFetch (iBase): IBM i XMLSERVICE call DB2 fetch results/rows of SQL statment. 61 | class iSqlParm (iBase): Parameter child node for iSqlExecute. 62 | class iSqlFree (iBase): IBM i XMLSERVICE call DB2 free open handles. 63 | 64 | Anything (XMLSERVICE XML, if no class exists): 65 | class iXml(iBase): IBM i XMLSERVICE raw xml input. 66 | 67 | Import: 68 | 1) XMLSERVICE direct call (current job) - local only 69 | from itoolkit import * 70 | from itoolkit.transport import DirectTransport* 71 | itransport = DirectTransport() 72 | Note: 73 | XMLSERVICE library search order: 74 | 1) environment variable 'XMLSERVICE' (export XMLSERVICE=QXMLSERV) 75 | 2) QXMLSERV -- IBM PTF library (DG1 PTFs) 76 | 3) XMLSERVICE -- download library (crtxml) 77 | 78 | 2) XMLSERVICE db2 call (QSQSRVR job) - local/remote 79 | from itoolkit import * 80 | from itoolkit.transport import DatabaseTransport 81 | # Connect to any PEP-249 compliant driver which can call XMLSERVICE stored procedures 82 | # eg. ibm_db_dbi 83 | import ibm_db_dbi 84 | conn = ibm_db_dbi.connect() 85 | 86 | # eg. or PyODBC 87 | import pyodbc 88 | conn = pyodbc.connect('DSN=mydsn;UID=...') 89 | 90 | itransport = DatabaseTransport(conn) 91 | Note: 92 | XMLSERVICE library search order: 93 | 1) lib parm -- DatabaseTransport(...,'XMLSERVICE') 94 | 1) environment variable 'XMLSERVICE' (export XMLSERVICE=XMLSERVICE) 95 | 2) QXMLSERV -- IBM PTF library (DG1 PTFs) 96 | 97 | 3) XMLSERVICE http/rest/web call (Apache job) - local/remote 98 | from itoolkit import * 99 | from itoolkit.transport import HttpTransport 100 | itransport = HttpTransport(url,user,password) 101 | 102 | Samples (itoolkit/sample): 103 | > export PATH=/QOpenSys/python/bin:$PATH 104 | > export LIBPATH=/QOpenSys/python/lib 105 | > cd /QOpenSys/python/lib/python3.4/site-packages/itoolkit-1.0-py3.4.egg/itoolkit/doc 106 | > python icmd5250_dspsyssts.py 107 | 108 | Tests (itoolkit/test): 109 | > export PATH=/QOpenSys/python/bin:$PATH 110 | > export LIBPATH=/QOpenSys/python/lib 111 | > cd /QOpenSys/python/lib/python3.4/site-packages/itoolkit-1.0-py3.4.egg/itoolkit/sample 112 | > python tests.py 113 | 114 | Debug (itoolkit/sample): 115 | # itoolkit/sample/istop_msg_qsysopr_before_pgm_call.py 116 | itransport = DirectTransport("*here *debug") # i will stop, inquiry message qsysopr 117 | 118 | -------------------------------------------------------------------------------- /src/itoolkit/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.0.0-dev" 2 | 3 | from .toolkit import iToolKit 4 | from .command import iCmd 5 | from .shell import iCmd5250 6 | from .shell import iSh 7 | from .program_call import iPgm 8 | from .program_call import iSrvPgm 9 | from .program_call import iRet 10 | from .program_call import iDS 11 | from .program_call import iData 12 | from .sql import iSqlQuery 13 | from .sql import iSqlExecute 14 | from .sql import iSqlPrepare 15 | from .sql import iSqlFetch 16 | from .sql import iSqlFree 17 | from .sql import iSqlParm 18 | from .base import iXml 19 | from .errors import TransportError 20 | from .errors import TransportClosedException 21 | 22 | __all__ = [ 23 | 'iToolKit', 24 | 'iCmd', 25 | 'iCmd5250', 26 | 'iSh', 27 | 'iPgm', 28 | 'iSrvPgm', 29 | 'iRet', 30 | 'iDS', 31 | 'iData', 32 | 'iSqlQuery', 33 | 'iSqlPrepare', 34 | 'iSqlExecute', 35 | 'iSqlFetch', 36 | 'iSqlParm', 37 | 'iSqlFree', 38 | 'iXml', 39 | 'TransportError', 40 | 'TransportClosedException', 41 | ] 42 | -------------------------------------------------------------------------------- /src/itoolkit/base.py: -------------------------------------------------------------------------------- 1 | import xml.dom.minidom 2 | 3 | class iBase: # noqa N801 4 | """IBM i XMLSERVICE call addable operation(s). 5 | 6 | Args: 7 | iopt (dict): user options (see descendents) 8 | idft (dict): default options (see descendents) 9 | 10 | Example: 11 | Calling a CL command and shell script:: 12 | 13 | itransport = DirectTransport() 14 | itool = iToolKit() 15 | itool.add(iCmd('chglibl', 'CHGLIBL LIBL(XMLSERVICE)')) 16 | itool.add(iSh('ps', 'ps -ef')) 17 | ... so on ... 18 | itool.call(itransport) 19 | 20 | Notes: 21 | iopt (dict): XMLSERVICE elements, attributes and values 22 | '_tag' - element 23 | 'value' - value value 24 | 'id' - attribute 25 | 'children' - iBase children 26 | ... many more idft + iopt ... 27 | 'error' - 28 | """ 29 | 30 | def __init__(self, iopt={}, idft={}): 31 | # xml defaults 32 | self.opt = idft.copy() 33 | self.opt.update(iopt) 34 | 35 | # my children objects 36 | self.opt['_children'] = [] 37 | 38 | def add(self, obj): 39 | """Additional mini dom xml child nodes. 40 | 41 | Args: 42 | obj (iBase) : additional child object 43 | 44 | Example: 45 | Adding a program:: 46 | 47 | itool = iToolKit() 48 | itool.add( 49 | iPgm('zzcall','ZZCALL') <--- child of iToolkit 50 | .addParm(iData('INCHARA','1a','a')) <--- child of iPgm 51 | ) 52 | """ 53 | self.opt['_children'].append(obj) 54 | 55 | def xml_in(self): 56 | """Return XML string of collected mini dom xml child nodes. 57 | 58 | Returns: 59 | XML (str) 60 | """ 61 | return self.make().toxml() 62 | 63 | def make(self, doc=None): 64 | """Assemble coherent mini dom xml, including child nodes. 65 | 66 | Returns: 67 | xml.dom.minidom (obj) 68 | """ 69 | 70 | if not doc: 71 | doc = xml.dom.minidom.Document() 72 | 73 | return self._make(doc) 74 | 75 | def _make(self, doc): 76 | tag = doc.createElement(self.opt['_tag']) 77 | tag.setAttribute('var', self.opt['_id']) 78 | 79 | for attr, value in self.opt.items(): 80 | if not attr.startswith('_'): 81 | tag.setAttribute(attr, str(value)) 82 | 83 | if len(self.opt['_value']): 84 | value = doc.createCDATASection(self.opt['_value']) 85 | tag.appendChild(value) 86 | 87 | for child in self.opt['_children']: 88 | tag.appendChild(child.make(doc)) 89 | 90 | return tag 91 | 92 | 93 | class iXml(iBase): # noqa N801 94 | """ 95 | IBM i XMLSERVICE raw xml input. 96 | 97 | Args: 98 | ixml (str): custom XML for XMLSERVICE operation. 99 | 100 | Example: 101 | iXml("CHGLIBL LIBL(XMLSERVICE)") 102 | iXml("ls /tmp") 103 | 104 | Returns: 105 | iXml (obj) 106 | 107 | Notes: 108 | Not commonly used, but ok when other classes fall short. 109 | """ 110 | 111 | def __init__(self, ixml): 112 | super().__init__() 113 | self.xml_body = ixml 114 | 115 | def add(self, obj): 116 | """add input not allowed. 117 | 118 | Returns: 119 | raise except 120 | """ 121 | raise 122 | 123 | def _make(self, _doc): 124 | """Assemble coherent mini dom xml. 125 | 126 | Returns: 127 | xml.dom.minidom (obj) 128 | """ 129 | try: 130 | node = xml.dom.minidom.parseString(self.xml_body).firstChild 131 | except xml.parsers.expat.ExpatError as e: 132 | e.args += (self.xml_body,) 133 | raise 134 | return node 135 | -------------------------------------------------------------------------------- /src/itoolkit/command.py: -------------------------------------------------------------------------------- 1 | from .base import iBase 2 | 3 | 4 | class iCmd(iBase): # noqa N801 5 | r""" 6 | IBM i XMLSERVICE call \*CMD not returning \*OUTPUT. 7 | 8 | Args: 9 | ikey (str): XML ...operation ... for parsing output. 10 | icmd (str): IBM i command no output (see 5250 command prompt). 11 | iopt (dict): option - dictionay of options (below) 12 | {'error':'on|off|fast'} : XMLSERVICE error option 13 | {'exec':cmd|system|rexx'} : XMLSERVICE command type {'exec':'cmd'} 14 | RTVJOBA CCSID(?N) {'exec':'rex'} 15 | 16 | Example: 17 | Example calling two CL commands:: 18 | 19 | iCmd('chglibl', 'CHGLIBL LIBL(XMLSERVICE) CURLIB(XMLSERVICE)') 20 | iCmd('rtvjoba', 'RTVJOBA CCSID(?N) OUTQ(?)') 21 | 22 | Returns: 23 | iCmd (obj) 24 | 25 | Notes: 26 | Special commands returning output parameters are allowed. 27 | (?) - indicate string return 28 | (?N) - indicate numeric return 29 | 30 | IBM i command 34 | """ 35 | 36 | def __init__(self, ikey, icmd, iopt={}): 37 | opts = { 38 | '_id': ikey, 39 | '_tag': 'cmd', 40 | '_value': icmd, 41 | 'exec': 'rexx' if '?' in icmd else 'cmd', 42 | 'error': 'fast' 43 | } 44 | super().__init__(iopt, opts) 45 | -------------------------------------------------------------------------------- /src/itoolkit/db2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/python-itoolkit/1c44e561b663c2ae36a2045d33bf378f8a025d13/src/itoolkit/db2/__init__.py -------------------------------------------------------------------------------- /src/itoolkit/db2/idb2call.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import warnings 3 | import os 4 | try: 5 | import ibm_db 6 | from ibm_db_dbi import connect, Connection 7 | except ImportError: 8 | pass 9 | from ..transport.database import DatabaseTransport 10 | 11 | 12 | class iDB2Call(DatabaseTransport): # noqa N801 13 | """ 14 | Transport XMLSERVICE calls over DB2 connection. 15 | 16 | Args: 17 | iuid (str): Database user profile name or database connection 18 | ipwd (str): optional - Database user profile password 19 | -- or -- 20 | env var PASSWORD (export PASSWORD=mypass) 21 | idb2 (str): optional - Database (WRKRDBDIRE *LOCAL) 22 | ictl (str): optional - XMLSERVICE control ['*here','*sbmjob'] 23 | ipc (str): optional - XMLSERVICE route for *sbmjob '/tmp/myunique' 24 | isiz (int): optional - XMLSERVICE expected max XML output size 25 | ilib (str): optional - XMLSERVICE library compiled (default QXMLSERV) 26 | 27 | Example: 28 | from itoolkit.db2.idb2call import * 29 | itransport = iDB2Call(user,password) 30 | -- or -- 31 | conn = ibm_db.connect(database, user, password) 32 | itransport = iDB2Call(conn) 33 | 34 | Returns: 35 | (obj) 36 | """ 37 | def __init__(self, iuid=None, ipwd=None, idb2='*LOCAL', ictl='*here *cdata', 38 | ipc='*na', isiz=None, ilib=None): 39 | warnings.warn( 40 | "iDB2Call is deprecated, " 41 | "use itoolkit.transport.DatabaseTransport instead", 42 | category=DeprecationWarning, 43 | stacklevel=2) 44 | 45 | if hasattr(iuid, 'cursor'): 46 | # iuid is a PEP-249 connection object, just store it 47 | conn = iuid 48 | elif isinstance(iuid, ibm_db.IBM_DBConnection): 49 | # iuid is a ibm_db connection object, wrap it in an 50 | # ibm_db_dbi connection object 51 | conn = Connection(iuid) 52 | else: 53 | # user id and password passed, connect using ibm_db_dbi 54 | ipwd = ipwd if ipwd else os.getenv('PASSWORD', None) 55 | conn = connect(database=idb2, user=iuid, password=ipwd) 56 | 57 | if ilib is None: 58 | ilib = os.getenv('XMLSERVICE', 'QXMLSERV') 59 | 60 | if isiz is not None: 61 | msg = "isiz is deprecated and ignored" 62 | warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 63 | 64 | super().__init__(conn=conn, ctl=ictl, ipc=ipc, schema=ilib) 65 | 66 | def call(self, itool): 67 | """Call XMLSERVICE with accumulated actions. 68 | 69 | Args: 70 | itool: An iToolkit object 71 | 72 | Returns: 73 | The XML returned from XMLSERVICE 74 | """ 75 | return super().call(itool) 76 | -------------------------------------------------------------------------------- /src/itoolkit/errors.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'TransportError', 3 | 'TransportClosedException', 4 | ] 5 | 6 | class TransportError(Exception): 7 | """Base exception class for all transport errors 8 | 9 | .. versionadded:: 1.7.1 10 | """ 11 | pass 12 | 13 | class TransportClosedException(TransportError): 14 | """Alias of :py:exc:`itoolkit.transport.TransportClosedError` 15 | 16 | .. deprecated:: 1.7.1 17 | Use :py:exc:`itoolkit.transport.TransportClosedError` instead. 18 | """ 19 | pass 20 | -------------------------------------------------------------------------------- /src/itoolkit/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/python-itoolkit/1c44e561b663c2ae36a2045d33bf378f8a025d13/src/itoolkit/lib/__init__.py -------------------------------------------------------------------------------- /src/itoolkit/lib/ilibcall.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import warnings 3 | from ..transport.direct import DirectTransport 4 | 5 | 6 | class iLibCall(DirectTransport): # noqa N801 gotta live with history 7 | """ 8 | Transport XMLSERVICE direct job call (within job/process calls). 9 | 10 | Args: 11 | ictl (str): optional - XMLSERVICE control ['*here','*sbmjob'] 12 | ipc (str): optional - XMLSERVICE job route for *sbmjob '/tmp/myunique' 13 | iccsid (int): optional - XMLSERVICE EBCDIC CCSID (0 = default jobccsid) 14 | pccsid (int): optional - XMLSERVICE ASCII CCSID 15 | """ 16 | def __init__(self, ictl='*here *cdata', ipc='*na', iccsid=0, pccsid=1208): 17 | warnings.warn( 18 | "iLibCall is deprecated, " 19 | "use itoolkit.transport.DirectTransport instead", 20 | category=DeprecationWarning, 21 | stacklevel=2) 22 | 23 | if iccsid != 0: 24 | raise ValueError("iccsid must be 0 (job ccsid)") 25 | 26 | if pccsid != 1208: 27 | raise ValueError("pccsid must be 1208 (UTF-8)") 28 | 29 | super().__init__(ctl=ictl, ipc=ipc) 30 | 31 | def call(self, itool): 32 | """Call XMLSERVICE with accumulated actions. 33 | 34 | Args: 35 | itool: An iToolkit object 36 | 37 | Returns: 38 | The XML returned from XMLSERVICE 39 | """ 40 | return super().call(itool) 41 | -------------------------------------------------------------------------------- /src/itoolkit/program_call.py: -------------------------------------------------------------------------------- 1 | from .base import iBase 2 | 3 | 4 | class iPgm (iBase): # noqa N801 5 | r""" 6 | IBM i XMLSERVICE call \*PGM. 7 | 8 | Args: 9 | ikey (str): XML ...operation ... for parsing output. 10 | iname (str): IBM i \*PGM or \*SRVPGM name 11 | iopt (dict): option - dictionay of options (below) 12 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 13 | {'func':'MYFUNC'} : IBM i \*SRVPGM function export. 14 | {'lib':'mylib'} : IBM i library name 15 | {'mode':'opm|ile'} : XMLSERVICE error choice {'mode':'ile'} 16 | 17 | Example: 18 | Example calling the `ZZCALL` program with 5 arguments:: 19 | 20 | iPgm('zzcall','ZZCALL') 21 | .addParm(iData('var1','1a','a')) 22 | .addParm(iData('var2','1a','b')) 23 | .addParm(iData('var3','7p4','32.1234')) 24 | .addParm(iData('var4','12p2','33.33')) 25 | .addParm(iDS('var5') 26 | .addData(iData('d5var1','1a','a')) 27 | .addData(iData('d5var2','1a','b')) 28 | .addData(iData('d5var3','7p4','32.1234')) 29 | .addData(iData('d5var4','12p2','33.33')) 30 | ) 31 | """ 32 | 33 | def __init__(self, ikey, iname, iopt={}): 34 | opts = { 35 | '_id': ikey, 36 | '_tag': 'pgm', 37 | '_value': '', 38 | 'name': iname, 39 | 'error': 'fast' 40 | } 41 | super().__init__(iopt, opts) 42 | self.pcnt = 0 43 | 44 | def addParm(self, obj, iopt={}): # noqa N802 45 | """Add a parameter child node. 46 | 47 | Args: 48 | obj (obj): iData object or iDs object. 49 | iopt (dict): options to pass to iParm constructor 50 | """ 51 | self.pcnt += 1 52 | p = iParm('p' + str(self.pcnt), iopt) 53 | p.add(obj) 54 | self.add(p) 55 | return self 56 | 57 | 58 | class iSrvPgm (iPgm): # noqa N801 59 | r""" 60 | IBM i XMLSERVICE call \*SRVPGM. 61 | 62 | Args: 63 | ikey (str): XML ...operation ... for parsing output. 64 | iname (str): IBM i \*PGM or \*SRVPGM name 65 | ifunc (str): IBM i \*SRVPGM function export. 66 | iopt (dict): option - dictionay of options (below) 67 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 68 | {'lib':'mylib'} : IBM i library name 69 | {'mode':'opm|ile'} : XMLSERVICE error choice {'mode':'ile'} 70 | 71 | Example: 72 | see iPgm 73 | 74 | Returns: 75 | iSrvPgm (obj) 76 | 77 | Notes: 78 | pgm: 79 | ... 85 | """ 86 | 87 | def __init__(self, ikey, iname, ifunc, iopt={}): 88 | iopt = iopt.copy() 89 | iopt['func'] = ifunc 90 | super().__init__(ikey, iname, iopt) 91 | 92 | def addRet(self, obj): # noqa N802 93 | """Add a return structure child node. 94 | 95 | Args: 96 | obj (obj): iData object or iDs object. 97 | """ 98 | self.pcnt += 1 99 | p = iRet('r' + str(self.pcnt)) 100 | p.add(obj) 101 | self.add(p) 102 | return self 103 | 104 | 105 | class iParm (iBase): # noqa N801 106 | """ 107 | Parameter child node for iPgm or iSrvPgm (see iPgm.addParm) 108 | 109 | Args: 110 | ikey (str): ikey (str): XML for parsing output. 111 | iopt (dict): option - dictionay of options (below) 112 | {'io':'in|out|both|omit'} : XMLSERVICE param type {'io':both'}. 113 | 114 | Example: 115 | see iPgm 116 | 117 | Returns: 118 | iParm (obj) 119 | 120 | Notes: 121 | This class is not used directly, but is used by iPgm.addParm 122 | or iSrvPgm.addParm. 123 | 124 | pgm parameters: 125 | 126 | (see and ) 129 | 130 | """ 131 | 132 | def __init__(self, ikey, iopt={}): 133 | opts = { 134 | '_id': ikey, 135 | '_tag': 'parm', 136 | '_value': '', 137 | 'io': 'both' 138 | } 139 | super().__init__(iopt, opts) 140 | 141 | 142 | class iRet (iBase): # noqa N801 143 | """ 144 | Return structure child node for iSrvPgm (see iSrvPgm.addRet) 145 | 146 | Args: 147 | ikey (str): XML for parsing output. 148 | 149 | Example: 150 | see iPgm 151 | 152 | Returns: 153 | iRet (obj) 154 | 155 | Notes: 156 | This class is not used directly, but is used by iSrvPgm.addRet. 157 | 158 | pgm return: 159 | 160 | (see and ) 161 | 162 | """ 163 | 164 | def __init__(self, ikey): 165 | opts = { 166 | '_id': ikey, 167 | '_tag': 'return', 168 | '_value': '' 169 | } 170 | super().__init__({}, opts) 171 | 172 | 173 | class iDS (iBase): # noqa N801 174 | """ 175 | Data structure child node for iPgm, iSrvPgm, 176 | or nested iDS data structures. 177 | 178 | Args: 179 | ikey (str): XML for parsing output. 180 | iopt (dict): option - dictionay of options (below) 181 | {'dim':'n'} : XMLSERVICE dimension/occurs number. 182 | {'dou':'label'} : XMLSERVICE do until label. 183 | {'len':'label'} : XMLSERVICE calc length label. 184 | 185 | Example: 186 | see iPgm 187 | 188 | Returns: 189 | iDS (obj) 190 | 191 | Notes: 192 | pgm data structure: 193 | (see ) 197 | """ 198 | 199 | def __init__(self, ikey, iopt={}): 200 | opts = { 201 | '_id': ikey, 202 | '_tag': 'ds', 203 | '_value': '' 204 | } 205 | super().__init__(iopt, opts) 206 | 207 | def addData(self, obj): # noqa N802 208 | """Add a iData or iDS child node. 209 | 210 | Args: 211 | obj (obj): iData object or iDs object. 212 | """ 213 | self.add(obj) 214 | return self 215 | 216 | 217 | class iData (iBase): # noqa N801 218 | """ 219 | Data value child node for iPgm, iSrvPgm, 220 | or iDS data structures. 221 | 222 | Args: 223 | ikey (str): XML for parsing output. 224 | iparm (obj): dom for parameter or return or ds. 225 | itype (obj): data type [see XMLSERVICE types, '3i0', ...]. 226 | ival (obj): data type value. 227 | iopt (dict): option - dictionay of options (below) 228 | {'dim':'n'} : XMLSERVICE dimension/occurs number. 229 | {'varying':'on|off|2|4'} : XMLSERVICE varying {'varying':'off'}. 230 | {'hex':'on|off'} : XMLSERVICE hex chracter data {'hex':'off'}. 231 | {'enddo':'label'} : XMLSERVICE enddo until label. 232 | {'setlen':'label'} : XMLSERVICE set calc length label. 233 | {'offset':'n'} : XMLSERVICE offset label. 234 | {'next':'label'} : XMLSERVICE next offset label (value). 235 | 236 | Example: 237 | see iPgm 238 | 239 | Returns: 240 | iData (obj) 241 | 242 | Notes: 243 | pgm data elements: 244 | (value) 256 | 257 | For more info on data types you can use, refer to 258 | http://yips.idevcloud.com/wiki/index.php/XMLService/DataTypes 259 | 260 | .. versionchanged:: 1.6.3 261 | `ival` is now optional and supports non-string parameters. 262 | """ 263 | 264 | def __init__(self, ikey, itype, ival="", iopt={}): 265 | opts = { 266 | '_id': ikey, 267 | '_tag': 'data', 268 | '_value': str(ival) if ival is not None else "", 269 | 'type': itype 270 | } 271 | super().__init__(iopt, opts) 272 | -------------------------------------------------------------------------------- /src/itoolkit/rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/python-itoolkit/1c44e561b663c2ae36a2045d33bf378f8a025d13/src/itoolkit/rest/__init__.py -------------------------------------------------------------------------------- /src/itoolkit/rest/irestcall.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import warnings 3 | import os 4 | from ..transport.http import HttpTransport 5 | 6 | 7 | class iRestCall(HttpTransport): # noqa N801 8 | """ 9 | Transport XMLSERVICE calls over standard HTTP rest. 10 | 11 | Args: 12 | iurl (str): XMLSERVICE url, eg. https://example.com/cgi-bin/xmlcgi.pgm 13 | iuid (str): Database user profile name 14 | ipwd (str): optional - Database user profile password 15 | -- or -- 16 | env var PASSWORD (export PASSWORD=mypass) 17 | idb2 (str): optional - Database (WRKRDBDIRE *LOCAL) 18 | ictl (str): optional - XMLSERVICE control ['*here','*sbmjob'] 19 | ipc (str): optional - XMLSERVICE route for *sbmjob '/tmp/myunique' 20 | isiz (str): optional - XMLSERVICE expected max XML output size 21 | 22 | Example: 23 | from itoolkit.rest.irestcall import * 24 | itransport = iRestCall(url,user,password) 25 | """ 26 | def __init__(self, iurl, iuid, ipwd=None, idb2=0, ictl=0, ipc=0, isiz=0): 27 | warnings.warn( 28 | "iRestCall is deprecated, " 29 | "use itoolkit.transport.HttpTransport instead", 30 | category=DeprecationWarning, 31 | stacklevel=2) 32 | 33 | if not ictl: 34 | ictl = '*here *cdata' 35 | 36 | if not ipc: 37 | ipc = '*na' 38 | 39 | if not ipwd: 40 | ipwd = os.environ['PASSWORD'] 41 | 42 | if not idb2: 43 | idb2 = '*LOCAL' 44 | 45 | if isiz not in (0, self.OUT_SIZE): 46 | msg = "isiz is deprecated, changed to {}".format(self.OUT_SIZE) 47 | warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 48 | 49 | super().__init__(url=iurl, user=iuid, password=ipwd, database=idb2, 50 | ctl=ictl, ipc=ipc) 51 | 52 | def call(self, itool): 53 | """Call XMLSERVICE with accumulated actions. 54 | 55 | Args: 56 | itool: An iToolkit object 57 | 58 | Returns: 59 | The XML returned from XMLSERVICE 60 | """ 61 | return super().call(itool) 62 | -------------------------------------------------------------------------------- /src/itoolkit/shell.py: -------------------------------------------------------------------------------- 1 | from .base import iBase 2 | 3 | try: 4 | from shlex import quote 5 | except ImportError: 6 | # python2 has shlex, but not shlex.quote 7 | # Implement a crude equivalent. We don't care about Python 2 that much 8 | def quote(s): 9 | if ' ' not in s: 10 | return s 11 | 12 | # remove first and last space to be less confusing 13 | quote_replacement = """ '"'"' """[1:-1] 14 | return "'" + s.replace("'", quote_replacement) + "'" 15 | 16 | 17 | class iSh(iBase): # noqa N801 18 | """ 19 | IBM i XMLSERVICE call PASE utilities. 20 | 21 | Args: 22 | ikey (str): XML ...operation ... for parsing output. 23 | icmd (str): IBM i PASE script/utility (see call qp2term). 24 | iopt (dict): option - dictionay of options (below) 25 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 26 | {'row':'on|off'} : XMLSERVICE wrap line in row tag? {'row':'off'} 27 | 28 | Example: 29 | iSh('ls /home/xml/master | grep -i xml') 30 | 31 | Returns: 32 | iSh (obj) 33 | 34 | Notes: 35 | XMLSERVICE perfoms standard PASE shell popen calls, 36 | therefore, additional job will be forked, 37 | utilities will be exec'd, and stdout will 38 | be collected to be returned. 39 | 40 | Please note, this is a relatively slow operation, 41 | use sparingly on high volume web sites. 42 | 43 | (PASE utility) 47 | """ 48 | 49 | def __init__(self, ikey, icmd, iopt={}): 50 | opts = { 51 | '_id': ikey, 52 | '_tag': 'sh', 53 | '_value': icmd, 54 | 'error': 'fast' 55 | } 56 | super().__init__(iopt, opts) 57 | 58 | 59 | class iCmd5250(iSh): # noqa N801 60 | r""" 61 | IBM i XMLSERVICE call 5250 \*CMD returning \*OUTPUT. 62 | 63 | Args: 64 | ikey (str): XML ...operation ... for parsing output. 65 | icmd (str): IBM i PASE script/utility (see call qp2term). 66 | iopt (dict): option - dictionay of options (below) 67 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 68 | {'row':'on|off'} : XMLSERVICE wrap line in row tag? {'row':'off'} 69 | 70 | Example: 71 | iCmd5250('dsplibl','dsplibl') 72 | iCmd5250('wrkactjob','wrkactjob') 73 | 74 | Returns: 75 | iCmd5250 (obj) 76 | 77 | Notes: 78 | This is a subclass of iSh, therefore XMLSERVICE perfoms 79 | standard PASE shell popen fork/exec calls. 80 | 81 | /QOpenSys/usr/bin/system 'wrkactjob' 82 | 83 | Please note, this is a relatively slow operation, 84 | use sparingly on high volume web sites. 85 | 86 | (PASE utility) 90 | """ 91 | 92 | def __init__(self, ikey, icmd, iopt={}): 93 | cmd = "/QOpenSys/usr/bin/system " + quote(icmd) 94 | super().__init__(ikey, cmd, iopt) 95 | -------------------------------------------------------------------------------- /src/itoolkit/sql.py: -------------------------------------------------------------------------------- 1 | import xml.dom.minidom 2 | 3 | from .base import iBase 4 | 5 | 6 | class SqlBaseAction (iBase): 7 | def _make(self, doc): 8 | node = super()._make(doc) 9 | 10 | # Create a surrounding tag 11 | sql_node = doc.createElement('sql') 12 | sql_node.setAttribute('var', self.opt['_id']) 13 | sql_node.appendChild(node) 14 | 15 | return sql_node 16 | 17 | class iSqlQuery (SqlBaseAction): # noqa N801 18 | """ 19 | IBM i XMLSERVICE call DB2 execute direct SQL statement. 20 | 21 | Args: 22 | ikey (str): XML ...operation ... for parsing output. 23 | isql (str): IBM i query (see 5250 strsql). 24 | iopt (dict): option - dictionay of options (below) 25 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 26 | {'conn':'label'} : XMLSERVICE connection label 27 | {'stmt':'label'} : XMLSERVICE stmt label 28 | {'options':'label'} : XMLSERVICE options label 29 | 30 | Example: 31 | iSqlQuery('custquery', "select * from QIWS.QCUSTCDT where LSTNAM='Jones'") 32 | iSqlFetch('custfetch') 33 | 34 | Returns: 35 | iSqlQuery (obj) 36 | """ 37 | 38 | def __init__(self, ikey, isql, iopt={}): 39 | opts = { 40 | '_id': ikey, 41 | '_tag': 'query', 42 | '_value': isql, 43 | 'error': 'fast' 44 | } 45 | super().__init__(iopt, opts) 46 | 47 | 48 | class iSqlPrepare (SqlBaseAction): # noqa N801 49 | """ 50 | IBM i XMLSERVICE call DB2 prepare SQL statement. 51 | 52 | Args: 53 | ikey (str): XML ...operation ... for parsing output. 54 | isql (str): IBM i query (see 5250 strsql). 55 | iopt (dict): option - dictionay of options (below) 56 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 57 | {'conn':'label'} : XMLSERVICE connection label 58 | {'stmt':'label'} : XMLSERVICE stmt label 59 | {'options':'label'} : XMLSERVICE options label 60 | 61 | Example: 62 | iSqlPrepare('callprep', "call mylib/mycall(?,?,?)") 63 | iSqlExecute('callexec') 64 | .addParm(iSqlParm('var1','a')) 65 | .addParm(iSqlParm('var2','b')) 66 | .addParm(iSqlParm('var3','32.1234')) 67 | iSqlFetch('callfetch') 68 | iSqlFree('alldone') 69 | 70 | Returns: 71 | iSqlPrepare (obj) 72 | """ 73 | 74 | def __init__(self, ikey, isql, iopt={}): 75 | opts = { 76 | '_id': ikey, 77 | '_tag': 'prepare', 78 | '_value': isql, 79 | 'error': 'fast' 80 | } 81 | super().__init__(iopt, opts) 82 | 83 | 84 | class iSqlExecute (SqlBaseAction): # noqa N801 85 | """ 86 | IBM i XMLSERVICE call execute a DB2 prepare SQL statement. 87 | 88 | Args: 89 | ikey (str): XML ...operation ... for parsing output. 90 | iopt (dict): option - dictionay of options (below) 91 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 92 | {'conn':'label'} : XMLSERVICE connection label 93 | {'stmt':'label'} : XMLSERVICE stmt label 94 | {'options':'label'} : XMLSERVICE options label 95 | 96 | Example: 97 | see iSqlPrepare 98 | 99 | Returns: 100 | iSqlExecute (obj) 101 | 102 | Notes: 103 | 104 | """ 105 | 106 | def __init__(self, ikey, iopt={}): 107 | opts = { 108 | '_id': ikey, 109 | '_tag': 'execute', 110 | '_value': '', 111 | 'error': 'fast' 112 | } 113 | super().__init__(iopt, opts) 114 | self.pcnt = 0 115 | 116 | def addParm(self, obj): # noqa N802 117 | """Add a iSqlParm child node. 118 | 119 | Args: 120 | obj (obj): iSqlParm object. 121 | """ 122 | self.add(obj) 123 | return self 124 | 125 | 126 | class iSqlFetch (SqlBaseAction): # noqa N801 127 | """ 128 | IBM i XMLSERVICE call DB2 fetch results/rows of SQL statement. 129 | 130 | Args: 131 | ikey (str): XML ...operation ... for parsing output. 132 | iopt (dict): option - dictionay of options (below) 133 | {'error':'on|off|fast'} : XMLSERVICE error choice {'error':'fast'} 134 | {'stmt':'label'} : XMLSERVICE stmt label 135 | {'block':'all|n'} : XMLSERVICE block records {'block':'all'} 136 | {'desc':'on|off'} : XMLSERVICE block records {'desc':'on'} 137 | {'rec':'n'} : XMLSERVICE block records 138 | 139 | Example: 140 | see iSqlPrepare or iSqlQuery 141 | """ 142 | 143 | def __init__(self, ikey, iopt={}): 144 | opts = { 145 | '_id': ikey, 146 | '_tag': 'fetch', 147 | '_value': '', 148 | 'block': 'all', 149 | 'error': 'fast' 150 | } 151 | super().__init__(iopt, opts) 152 | 153 | 154 | class iSqlParm (iBase): # noqa N801 155 | """ 156 | Parameter child node for iSqlExecute (see iSqlExecute.addParm) 157 | 158 | Args: 159 | ikey (str): XML for parsing output. 160 | ival (str): data value. 161 | iopt (dict): option - dictionay of options (below) 162 | {'io':'in|out|both|omit'} : XMLSERVICE param type {'io':both'}. 163 | 164 | Example: 165 | see iSqlPrepare 166 | 167 | Returns: 168 | iSqlParm (obj) 169 | 170 | Notes: 171 | This class is not used directly, but is used by iSqlExecute.addParm. 172 | 173 | 174 | 175 | val 176 | 177 | 178 | """ 179 | 180 | def __init__(self, ikey, ival, iopt={}): 181 | opts = { 182 | '_id': ikey, 183 | '_tag': 'parm', 184 | '_value': ival, 185 | 'io': 'both' 186 | } 187 | super().__init__(iopt, opts) 188 | 189 | 190 | class iSqlFree (SqlBaseAction): # noqa N801 191 | """ 192 | IBM i XMLSERVICE call DB2 free open handles. 193 | 194 | Args: 195 | ikey (str): XML ...operation ... for parsing output. 196 | iopt (dict): option - dictionay of options (below) 197 | {'error':'on|off|fast'} : XMLSERVICE error choice 198 | {'conn':'all|label'} : XMLSERVICE free connection label 199 | {'cstmt':'label'} : XMLSERVICE free connection label statements 200 | {'stmt':'all|label'} : XMLSERVICE free stmt label 201 | {'options':'all|label'} : XMLSERVICE free options label 202 | 203 | Example: 204 | see iSqlPrepare 205 | 206 | Returns: 207 | iSqlFree (obj) 208 | 209 | Notes: 210 | 211 | 216 | 217 | """ 218 | 219 | def __init__(self, ikey, iopt={}): 220 | opts = { 221 | '_id': ikey, 222 | '_tag': 'free', 223 | '_value': '', 224 | 'error': 'fast' 225 | } 226 | super().__init__(iopt, opts) 227 | -------------------------------------------------------------------------------- /src/itoolkit/toolkit.py: -------------------------------------------------------------------------------- 1 | 2 | import xml.dom.minidom 3 | import re 4 | import sys 5 | import time 6 | import logging 7 | import warnings 8 | 9 | 10 | class iToolKit: # noqa N801 11 | """ 12 | Main iToolKit XMLSERVICE collector and output parser. 13 | 14 | Args: 15 | iparm (num): include xml node parm output (0-no, 1-yes). 16 | iret (num): include xml node return output (0-no, 1-yes). 17 | ids (num): include xml node ds output (0-no, 1-yes). 18 | irow (num): include xml node row output (0-no, 1-yes). 19 | 20 | Returns: 21 | iToolKit (obj) 22 | """ 23 | 24 | def __init__(self, iparm=0, iret=0, ids=1, irow=1): 25 | # XMLSERVICE 26 | self.data_keys = ["cmd", "pgm", "sh", "sql"] 27 | self.data_vals = [ 28 | "cmd", 29 | "sh", 30 | "data", 31 | "success", 32 | "error", 33 | "xmlhint", 34 | "jobipcskey", 35 | "jobname", 36 | "jobuser", 37 | "jobnbr", 38 | "curuser", 39 | "ccsid", 40 | "dftccsid", 41 | "paseccsid", 42 | "joblog", 43 | "jobipc", 44 | "syslibl", 45 | "usrlibl", 46 | "version", 47 | "jobcpf"] 48 | if iparm: 49 | self.data_keys.append("parm") 50 | self.data_vals.append("parm") 51 | if iret: 52 | self.data_keys.append("return") 53 | if irow: 54 | self.data_keys.append("row") 55 | self.data_vals.append("row") 56 | if ids: 57 | self.data_keys.append("ds") 58 | 59 | self.input = [] 60 | self.domo = "" 61 | 62 | self.logger = logging.getLogger('itoolkit-trace') 63 | self.trace_handler = None 64 | 65 | def clear(self): 66 | """Clear collecting child objects. 67 | 68 | Notes: 69 | 70 | 71 | """ 72 | # XMLSERVICE 73 | self.input = [] 74 | self.domo = "" 75 | 76 | def add(self, obj): 77 | """Add additional child object. 78 | 79 | Args: 80 | obj 81 | 82 | Notes: 83 | 84 | 85 | """ 86 | self.input.append(obj) 87 | 88 | def xml_in(self): 89 | """return raw xml input. 90 | 91 | Returns: 92 | xml 93 | """ 94 | doc = xml.dom.minidom.Document() 95 | 96 | root = doc.createElement("xmlservice") 97 | doc.appendChild(root) 98 | 99 | for item in self.input: 100 | root.appendChild(item._make(doc)) 101 | 102 | return doc.toxml() 103 | 104 | def xml_out(self): 105 | """return raw xml output. 106 | 107 | Returns: 108 | xml 109 | """ 110 | domo = self._dom_out() 111 | return domo.toxml() 112 | 113 | def list_out(self, ikey=-1): 114 | """return list output. 115 | 116 | Args: 117 | ikey (num): select list from index [[0],[1],,,,]. 118 | 119 | Returns: 120 | list [value] 121 | """ 122 | output = [] 123 | domo = self._dom_out() 124 | self._parseXmlList(domo, output) 125 | if ikey > -1: 126 | try: 127 | return output[ikey] 128 | except IndexError: 129 | output[ikey] = output 130 | return output[ikey] 131 | else: 132 | return output 133 | 134 | def dict_out(self, ikey=0): 135 | """return dict output. 136 | 137 | Args: 138 | ikey (str): select 'key' from {'key':'value'}. 139 | 140 | Returns: 141 | dict {'key':'value'} 142 | """ 143 | self.unq = 0 144 | output = {} 145 | domo = self._dom_out() 146 | self._parseXmlDict(domo, output) 147 | if isinstance(ikey, str): 148 | try: 149 | return output[ikey] 150 | except KeyError: 151 | output[ikey] = {'error': output} 152 | return output[ikey] 153 | else: 154 | return output 155 | 156 | def hybrid_out(self, ikey=0): 157 | """return hybrid output. 158 | 159 | Args: 160 | ikey (str): select 'key' from {'key':'value'}. 161 | 162 | Returns: 163 | hybrid {key:{'data':[list]}} 164 | """ 165 | self.unq = 0 166 | output = {} 167 | domo = self._dom_out() 168 | self._parseXmlHybrid(domo, output) 169 | if isinstance(ikey, str): 170 | try: 171 | return output[ikey] 172 | except KeyError: 173 | output[ikey] = {'error': output} 174 | return output[ikey] 175 | else: 176 | return output 177 | 178 | def trace_open(self, iname='*terminal'): 179 | r"""Open trace file. 180 | 181 | If ``iname`` is "\*terminal", trace will output to ``sys.stdout``. 182 | Otherwise, a file path with the format /tmp/python_toolkit_(iname).log 183 | is used to open a trace file in append mode. 184 | 185 | Args: 186 | iname (str): Name of trace file or "\*terminal" for ``sys.stdout`` 187 | 188 | .. versionadded:: 1.2 189 | .. deprecated:: 2.0 190 | See :ref:`Tracing `. 191 | """ 192 | warnings.warn( 193 | "trace_open is deprecated, use the logging module instead", 194 | category=DeprecationWarning, 195 | stacklevel=2) 196 | 197 | self.trace_close() 198 | 199 | if '*' in iname: 200 | self.trace_handler = logging.StreamHandler(sys.stdout) 201 | else: 202 | path = f'/tmp/python_toolkit_{iname}.log' 203 | self.trace_handler = logging.FileHandler(path) 204 | 205 | self.logger.setLevel(logging.INFO) 206 | self.logger.addHandler(self.trace_handler) 207 | 208 | def trace_close(self): 209 | """End trace and close trace file. 210 | 211 | .. versionadded:: 1.2 212 | .. deprecated:: 2.0 213 | See :ref:`Tracing `. 214 | """ 215 | warnings.warn("trace_close is deprecated", category=DeprecationWarning, 216 | stacklevel=2) 217 | 218 | if not self.trace_handler: 219 | return 220 | 221 | self.logger.removeHandler(self.trace_handler) 222 | try: 223 | self.trace_handler.close() 224 | except AttributeError: 225 | # logging.StreamHandler doesn't support close 226 | pass 227 | 228 | self.trace_handler = None 229 | 230 | def call(self, itrans): 231 | """Call xmlservice with accumulated input XML. 232 | 233 | Args: 234 | itrans (obj): XMLSERVICE transport object 235 | 236 | Raises: 237 | itoolkit.transport.TransportClosedError: If the transport has been 238 | closed. 239 | """ 240 | self.logger.info('***********************') 241 | self.logger.info('control ' + time.strftime("%c")) 242 | self.logger.info(itrans.trace_data()) 243 | self.logger.info('input ' + time.strftime("%c")) 244 | self.logger.info(self.xml_in()) 245 | 246 | # step 1 -- make call 247 | step = 1 248 | xml_out = itrans.call(self) 249 | if not (xml_out and xml_out.strip()): 250 | xml_out = """ 251 | 252 | *NODATA 253 | """ 254 | # step 1 -- parse return 255 | try: 256 | self.logger.info('output ' + time.strftime("%c")) 257 | self.logger.info(xml_out) 258 | domo = xml.dom.minidom.parseString(xml_out) 259 | except Exception: 260 | step = 2 261 | # step 2 -- bad parse, try modify bad output 262 | if step == 2: 263 | try: 264 | self.logger.info('parse (fail) ' + time.strftime("%c")) 265 | 266 | def to_printable(c): 267 | s = chr(c) 268 | return s if s.isprintable() else '.' 269 | 270 | data = xml_out.encode() 271 | for i in range(0, len(data), 16): 272 | chunk = data[i:i+16] 273 | 274 | hex = chunk.hex().ljust(32) 275 | text = "".join([to_printable(_) for _ in chunk]) 276 | self.logger.info(f'{hex} {text}') 277 | 278 | clean1 = re.sub(r'[\x00-\x1F\x3C\x3E]', ' ', xml_out) 279 | clean = re.sub(' +', ' ', clean1) 280 | xml_out2 = """ 281 | 282 | *BADPARSE 283 | 284 | """.format(clean) 285 | domo = xml.dom.minidom.parseString(xml_out2) 286 | except Exception: 287 | step = 3 288 | # step 3 -- horrible parse, give up on output 289 | if step == 3: 290 | xml_out2 = """ 291 | 292 | *NOPARSE 293 | """ 294 | domo = xml.dom.minidom.parseString(xml_out2) 295 | self.logger.info( 296 | 'parse step: ' + 297 | str(step) + 298 | ' (1-ok, 2-*BADPARSE, 3-*NOPARSE)') 299 | self.domo = domo 300 | 301 | def _dom_out(self): 302 | """return xmlservice dom output. 303 | 304 | Returns: 305 | xml.dom 306 | """ 307 | if self.domo == "" or self.domo is None: 308 | # something very bad happened 309 | xmlblank = "\n" 310 | xmlblank += "\n" 311 | xmlblank += "no output\n" 312 | xmlblank += "", " ") 315 | xmlblank += "]]>\n" 316 | xmlblank += "" 317 | self.domo = xml.dom.minidom.parseString(xmlblank) 318 | return self.domo 319 | 320 | def _parseXmlList(self, parent, values): # noqa N802 321 | """return dict output. 322 | 323 | Args: 324 | parent (obj): parent xml.dom 325 | values (obj): accumulate list [] 326 | 327 | Returns: 328 | list [value] 329 | """ 330 | for child in parent.childNodes: 331 | if child.nodeType in (child.TEXT_NODE, child.CDATA_SECTION_NODE): 332 | if child.parentNode.tagName in self.data_vals \ 333 | and child.nodeValue != "\n": 334 | values.append(child.nodeValue) 335 | elif child.nodeType == xml.dom.Node.ELEMENT_NODE: 336 | if child.tagName in self.data_keys: 337 | child_values = [] # values [v,v,...] 338 | values.append(child_values) 339 | self._parseXmlList(child, child_values) 340 | else: 341 | # make sure one empty data value (1.1) 342 | if child.tagName in self.data_vals and not( 343 | child.childNodes): 344 | child.appendChild(self.domo.createTextNode("")) 345 | self._parseXmlList(child, values) 346 | 347 | def _parseXmlDict(self, parent, values): # noqa N802 348 | """return dict output. 349 | 350 | Args: 351 | parent (obj): parent xml.dom 352 | values (obj): accumulate dict{} 353 | 354 | Returns: 355 | dict {'key':'value'} 356 | """ 357 | for child in parent.childNodes: 358 | if child.nodeType in (child.TEXT_NODE, child.CDATA_SECTION_NODE): 359 | if child.parentNode.tagName in self.data_vals \ 360 | and child.nodeValue != "\n": 361 | var = child.parentNode.getAttribute('var') 362 | if var == "": 363 | var = child.parentNode.getAttribute('desc') 364 | if var == "": 365 | var = child.parentNode.tagName 366 | # special for sql parms 367 | if child.parentNode.tagName in 'parm': 368 | var = "data" 369 | myvar = var 370 | while var in values: 371 | self.unq += 1 372 | var = myvar + str(self.unq) 373 | values[var] = child.nodeValue 374 | elif child.nodeType == xml.dom.Node.ELEMENT_NODE: 375 | if child.tagName in self.data_keys: 376 | var = child.getAttribute('var') 377 | if var == "": 378 | var = child.tagName 379 | # key was already in values 380 | # becomes list of same name 381 | child_values = {} 382 | if var in values: 383 | old = values[var] 384 | if isinstance(old, list): 385 | old.append(child_values) 386 | else: 387 | values[var] = [] 388 | values[var].append(old) 389 | values[var].append(child_values) 390 | else: 391 | values[var] = child_values 392 | self._parseXmlDict(child, child_values) 393 | else: 394 | # make sure one empty data value (1.1) 395 | if child.tagName in self.data_vals and not( 396 | child.childNodes): 397 | child.appendChild(self.domo.createTextNode("")) 398 | self._parseXmlDict(child, values) 399 | 400 | def _parseXmlHybrid(self, parent, values): # noqa N802 401 | """return dict output. 402 | 403 | Args: 404 | parent (obj): parent xml.dom 405 | values (obj): accumulate hybrid{} 406 | 407 | Returns: 408 | hybrid {key:{'data':[list]}} 409 | """ 410 | for child in parent.childNodes: 411 | if child.nodeType in (child.TEXT_NODE, child.CDATA_SECTION_NODE): 412 | if child.parentNode.tagName in self.data_vals \ 413 | and child.nodeValue != "\n": 414 | if 'data' not in values: 415 | values['data'] = [] 416 | values['data'].append(child.nodeValue) 417 | elif child.nodeType == xml.dom.Node.ELEMENT_NODE: 418 | if child.tagName in self.data_keys: 419 | var = child.getAttribute('var') 420 | if var == "": 421 | var = child.tagName 422 | # key was already in values 423 | # becomes list of same name 424 | child_values = {} 425 | if var in values: 426 | old = values[var] 427 | if isinstance(old, list): 428 | old.append(child_values) 429 | else: 430 | values[var] = [] 431 | values[var].append(old) 432 | values[var].append(child_values) 433 | else: 434 | values[var] = child_values 435 | self._parseXmlHybrid(child, child_values) 436 | else: 437 | # make sure one empty data value (1.1) 438 | if child.tagName in self.data_vals and not( 439 | child.childNodes): 440 | child.appendChild(self.domo.createTextNode("")) 441 | self._parseXmlHybrid(child, values) 442 | -------------------------------------------------------------------------------- /src/itoolkit/transport/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .base import XmlServiceTransport 3 | from .database import DatabaseTransport 4 | from .direct import DirectTransport 5 | from .http import HttpTransport 6 | from .ssh import SshTransport 7 | from .errors import TransportClosedError 8 | 9 | __all__ = [ 10 | 'XmlServiceTransport', 11 | 'DatabaseTransport', 12 | 'DirectTransport', 13 | 'HttpTransport', 14 | 'SshTransport', 15 | 'TransportClosedError', 16 | ] 17 | -------------------------------------------------------------------------------- /src/itoolkit/transport/_direct.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ctypes import c_int, c_uint, c_int16, c_ulonglong, c_void_p, c_char_p, \ 4 | CDLL, DEFAULT_MODE, POINTER, Structure, addressof, sizeof, \ 5 | create_string_buffer 6 | 7 | RTLD_MEMBER = 0x00040000 8 | 9 | 10 | class ILEPointer(Structure): 11 | "An ILE pointer type" 12 | _pack_ = 16 13 | _fields_ = [ 14 | ("hi", c_ulonglong), 15 | ("lo", c_ulonglong) 16 | ] 17 | 18 | 19 | try: 20 | _LIBC = CDLL("/QOpenSys/usr/lib/libc.a(shr_64.o)", 21 | DEFAULT_MODE | RTLD_MEMBER) 22 | 23 | _SETSPP = _LIBC._SETSPP 24 | _SETSPP.argtypes = [POINTER(ILEPointer), c_void_p] 25 | 26 | _ILELOADX = _LIBC._ILELOADX 27 | _ILELOADX.argtypes = [c_char_p, c_uint] 28 | _ILELOADX.restype = c_ulonglong 29 | 30 | _ILESYMX = _LIBC._ILESYMX 31 | _ILESYMX.argtypes = [POINTER(ILEPointer), c_ulonglong, c_char_p] 32 | 33 | _ILECALLX = _LIBC._ILECALLX 34 | _ILECALLX.argtypes = [ 35 | POINTER(ILEPointer), 36 | c_void_p, 37 | POINTER(c_int16), 38 | c_int16, 39 | c_int 40 | ] 41 | except OSError: 42 | # Either we couldn't load libc or we couldn't find the necessary syscalls 43 | # exported from libc. Either way, this platform is unsupported so we raise 44 | # an import error to prevent it from being used. 45 | raise ImportError 46 | 47 | ILELOAD_LIBOBJ = 0x00000001 48 | ILESYM_PROCEDURE = 1 49 | 50 | RESULT_VOID = 0 51 | RESULT_INT8 = -1 52 | RESULT_UINT8 = -2 53 | RESULT_INT16 = -3 54 | RESULT_UINT16 = -4 55 | RESULT_INT32 = -5 56 | RESULT_UINT32 = -6 57 | RESULT_INT64 = -7 58 | RESULT_UINT64 = -8 59 | RESULT_FLOAT64 = -10 60 | RESULT_FLOAT128 = -18 61 | 62 | ARG_END = 0 63 | ARG_MEMPTR = -11 64 | 65 | 66 | class MemPointer(ILEPointer): 67 | "An ILE pointer type to be used with ARG_MEMPTR" 68 | _pack_ = 16 69 | 70 | def __init__(self, addr=0): 71 | super().__int__() 72 | self.hi = 0 73 | self.lo = addr 74 | 75 | @property 76 | def addr(self): 77 | return self.lo 78 | 79 | @addr.setter 80 | def addr(self, addr): 81 | self.lo = addr 82 | 83 | 84 | class ILEArglistBase(Structure): 85 | "ILECALL argument list base member" 86 | _pack_ = 16 87 | _fields_ = [ 88 | ('descriptor', ILEPointer), 89 | ('result', ILEPointer), 90 | ] 91 | 92 | 93 | class RunASCIIArglist(Structure): 94 | "Argument list definition for the RUNASCII procedure" 95 | _pack_ = 16 96 | _fields_ = [ 97 | ('base', ILEArglistBase), 98 | ('ipc', MemPointer), 99 | ('ipc_len', MemPointer), 100 | ('ctl', MemPointer), 101 | ('ctl_len', MemPointer), 102 | ('xmlin', MemPointer), 103 | ('xmlin_len', MemPointer), 104 | ('xmlout', MemPointer), 105 | ('xmlout_len', MemPointer), 106 | ('pase_ccsid', MemPointer), 107 | ('ile_ccsid', MemPointer), 108 | ] 109 | 110 | 111 | RunASCIISignature = c_int16 * 11 112 | 113 | _SIGNATURE = RunASCIISignature( 114 | ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, 115 | ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, ARG_MEMPTR, 116 | ARG_END 117 | ) 118 | 119 | 120 | class _XMLSERVICE: 121 | def __init__(self, library): 122 | self.library = library 123 | 124 | path = "{library}/XMLSTOREDP".format(library=library) 125 | actgrp = _ILELOADX(path.encode(), ILELOAD_LIBOBJ) 126 | if actgrp == 0xffffffffffffffff: 127 | raise OSError("{path} not found".format(path=path)) 128 | 129 | self._RUNASCII = ILEPointer() 130 | if _ILESYMX(self._RUNASCII, actgrp, b"RUNASCII") != ILESYM_PROCEDURE: 131 | raise OSError("RUNASCII procedure not found in {path}" 132 | .format(path=path)) 133 | 134 | def __call__(self, xmlin, ipc, ctl): 135 | ipc = c_char_p(ipc.encode()) 136 | ipc_len = c_int(len(ipc.value)) 137 | 138 | ctl = c_char_p(ctl.encode()) 139 | ctl_len = c_int(len(ctl.value)) 140 | 141 | xmlin = c_char_p(xmlin.encode()) 142 | xmlin_len = c_int(len(xmlin.value)) 143 | 144 | xmlout = create_string_buffer(0x1000000) # 16MiB 145 | xmlout_len = c_int(sizeof(xmlout)) 146 | 147 | pase_ccsid = c_int(1208) 148 | ile_ccsid = c_int(0) 149 | 150 | # RUNASCII doesn't just take ILE pointers, it takes ILE pointers to ILE 151 | # pointers, so we first copy the PASE pointer in to a space pointer for 152 | # each pointer parameter... *except* for the integer parameters, which 153 | # are just pointers to integers for some reason... 154 | ipc_spp = ILEPointer() 155 | ctl_spp = ILEPointer() 156 | xmlin_spp = ILEPointer() 157 | xmlout_spp = ILEPointer() 158 | 159 | _SETSPP(ipc_spp, ipc) 160 | _SETSPP(ctl_spp, ctl) 161 | _SETSPP(xmlin_spp, xmlin) 162 | _SETSPP(xmlout_spp, xmlout) 163 | 164 | arglist = RunASCIIArglist() 165 | arglist.ipc.addr = addressof(ipc_spp) 166 | arglist.ipc_len.addr = addressof(ipc_len) 167 | arglist.ctl.addr = addressof(ctl_spp) 168 | arglist.ctl_len.addr = addressof(ctl_len) 169 | arglist.xmlin.addr = addressof(xmlin_spp) 170 | arglist.xmlin_len.addr = addressof(xmlin_len) 171 | arglist.xmlout.addr = addressof(xmlout_spp) 172 | arglist.xmlout_len.addr = addressof(xmlout_len) 173 | arglist.pase_ccsid.addr = addressof(pase_ccsid) 174 | arglist.ile_ccsid.addr = addressof(ile_ccsid) 175 | 176 | if _ILECALLX(self._RUNASCII, addressof(arglist), _SIGNATURE, 177 | RESULT_INT32, 0): 178 | raise RuntimeError("Failed to call XMLSERVICE with _ILECALL") 179 | 180 | if arglist.base.result.lo & 0xffffffff: 181 | raise RuntimeError("XMLSERVICE returned an error") 182 | 183 | return xmlout.value 184 | 185 | 186 | def xmlservice(xmlin, ipc, ctl): 187 | # If we haven't yet initialized XMLSERVICE, do that now 188 | if not hasattr(xmlservice, 'cached_obj'): 189 | 190 | libraries = [os.getenv("XMLSERVICE"), "QXMLSERV", "XMLSERVICE"] 191 | 192 | found = False 193 | for library in libraries: 194 | if not library: 195 | continue 196 | 197 | try: 198 | xmlservice.cached_obj = _XMLSERVICE(library) 199 | found = True 200 | except OSError: 201 | continue 202 | 203 | if not found: 204 | # TODO: Message string 205 | raise OSError("Couldn't load RUNASCII function from XMLSERVICE") 206 | 207 | return xmlservice.cached_obj(xmlin, ipc, ctl) 208 | -------------------------------------------------------------------------------- /src/itoolkit/transport/base.py: -------------------------------------------------------------------------------- 1 | from .errors import TransportClosedError 2 | 3 | 4 | class XmlServiceTransport: 5 | """XMLSERVICE transport base class 6 | 7 | Args: 8 | ctl (str): XMLSERVICE control options, see 9 | http://yips.idevcloud.com/wiki/index.php/XMLService/XMLSERVICEQuick#ctl 10 | ipc (str): An XMLSERVICE ipc key for stateful conections, see 11 | http://yips.idevcloud.com/wiki/index.php/XMLService/XMLSERVICEConnect 12 | """ 13 | def __init__(self, ctl="*here *cdata", ipc="*na"): 14 | self.ipc = ipc 15 | self.ctl = ctl 16 | 17 | self.trace_attrs = ["ipc", "ctl"] 18 | self._is_open = True 19 | 20 | def __del__(self): 21 | self.close() 22 | 23 | def trace_data(self): 24 | output = "" 25 | 26 | for i in self.trace_attrs: 27 | if isinstance(i, tuple): 28 | trace, attr = i 29 | else: 30 | trace = attr = i 31 | 32 | output += " {}({})".format(trace, getattr(self, attr)) 33 | 34 | return output 35 | 36 | def call(self, tk): 37 | """Call XMLSERVICE with accumulated actions. 38 | 39 | Args: 40 | tk (iToolKit): An iToolkit object 41 | 42 | Returns: 43 | str: The XML returned from XMLSERVICE 44 | 45 | Attention: 46 | Subclasses should implement :py:func:`_call` to call XMLSERVICE 47 | instead of overriding this method. 48 | """ 49 | self._ensure_open() 50 | 51 | return self._call(tk) 52 | 53 | def _call(self, tk): 54 | """Called by :py:func:`call`. This should be overridden by subclasses 55 | to the call function instead of overriding :py:func:`call` directly. 56 | """ 57 | raise NotImplementedError 58 | 59 | def _ensure_open(self): 60 | """This should be called by any subclass function which uses 61 | resources which may have been released when `close` is called.""" 62 | if not self._is_open: 63 | raise TransportClosedError() 64 | 65 | def close(self): 66 | """Close the connection now rather than when :py:func:`__del__` is 67 | called. 68 | 69 | The transport will be unusable from this point forward and a 70 | :py:exc:`itoolkit.transport.TransportClosedError` exception will be 71 | raised if any operation is attempted with the transport. 72 | 73 | Attention: 74 | Subclasses should implement :py:func:`_close` to free its resources 75 | instead of overriding this method. 76 | """ 77 | self._close() 78 | self._is_open = False 79 | 80 | def _close(self): 81 | """Called by `close`. This should be overridden by subclasses to close 82 | any resources specific to that implementation.""" 83 | pass 84 | -------------------------------------------------------------------------------- /src/itoolkit/transport/database.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | 4 | from .base import XmlServiceTransport 5 | 6 | __all__ = [ 7 | 'DatabaseTransport' 8 | ] 9 | 10 | 11 | class DatabaseTransport(XmlServiceTransport): 12 | r"""Call XMLSERVICE using a database connection 13 | 14 | Args: 15 | conn: An active database connection object (PEP-249) 16 | schema (str, optional): The XMLSERVICE stored procedure schema 17 | to use 18 | **kwargs: Base transport options. See `XmlServiceTransport`. 19 | Examples: 20 | Connecting to XMLSERVICE over ODBC with the default \*LOCAL DSN on 21 | IBM i. 22 | 23 | >>> from itoolkit.transport import DatabaseTransport 24 | >>> import pyodbc 25 | >>> transport = DatabaseTransport(pyodbc.connect('DSN=*LOCAL')) 26 | 27 | Connecting to XMLSERVICE with ibm_db_dbi on IBM i. 28 | 29 | >>> from itoolkit.transport import DatabaseTransport 30 | >>> import ibm_db_dbi 31 | >>> transport = DatabaseTransport(ibm_db_dbi.connect()) 32 | """ 33 | def __init__(self, conn, **kwargs): 34 | # TODO: When we drop Python 2 support, add `*, schema='QXMLSERV'` 35 | # to the function variables, to make schema a keyword-only argument 36 | # and remove this block of code 37 | if 'schema' in kwargs: 38 | schema = kwargs['schema'] 39 | del kwargs['schema'] 40 | else: 41 | schema = 'QXMLSERV' 42 | 43 | if not hasattr(conn, 'cursor'): 44 | raise ValueError( 45 | "conn must be a PEP-249 compliant connection object") 46 | 47 | if not isinstance(schema, str) and schema is not None: 48 | raise ValueError("schema must be a string or None") 49 | 50 | super().__init__(**kwargs) 51 | 52 | self.conn = conn 53 | 54 | self.procedure = "iPLUGR512K" 55 | if schema: 56 | self.procedure = schema + "." + self.procedure 57 | 58 | # We could simplify to just using execute, since we don't care 59 | # about output parameters, but ibm_db throws weird errors when 60 | # calling procedures with `execute` :shrug: 61 | if hasattr(self.conn.cursor(), 'callproc'): 62 | self.query = self.procedure 63 | self.func = 'callproc' 64 | else: 65 | self.query = "call {}(?,?,?)".format(self.procedure) 66 | self.func = 'execute' 67 | 68 | self.trace_attrs.extend([ 69 | ('proc', 'procedure') 70 | ]) 71 | 72 | def _call(self, tk): 73 | cursor = self.conn.cursor() 74 | 75 | parms = (self.ipc, self.ctl, tk.xml_in()) 76 | 77 | # call the procedure using the correct method for this 78 | # cursor type, which we ascertained in the constructor 79 | getattr(cursor, self.func)(self.query, parms) 80 | 81 | # Use fetchall since not all adapters support the PEP-249 cursor 82 | # iteration extension eg. JayDeBeApi 83 | return "".join(row[0] for row in cursor.fetchall()).rstrip('\0') 84 | 85 | def _close(self): 86 | try: 87 | self.conn.close() 88 | except RuntimeError: 89 | # JayDeBeApi can fail to close a connection with 90 | # jpype._core.JVMNotRunning: Java Virtual Machine is not running 91 | # 92 | # Doesn't seem to be anything reasonable to do but log the 93 | # exception and continue. 94 | logging.exception("Unexpected exception closing database connection") 95 | -------------------------------------------------------------------------------- /src/itoolkit/transport/direct.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .base import XmlServiceTransport 3 | 4 | try: 5 | # _direct will raise an ImportError when it is running on an unsupported 6 | # platform. This is fine, we check for this in DirectTransport.call 7 | from . import _direct 8 | except ImportError: 9 | pass 10 | 11 | __all__ = [ 12 | 'DirectTransport' 13 | ] 14 | 15 | 16 | class DirectTransport(XmlServiceTransport): 17 | """Call XMLSERVICE directly in-process using _ILECALL 18 | 19 | Args: 20 | **kwargs: Base transport options. See `XmlServiceTransport`. 21 | 22 | Example: 23 | >>> from itoolkit.transport import DirectTransport 24 | >>> transport = DirectTransport() 25 | """ 26 | def __init__(self, **kwargs): 27 | super().__init__(**kwargs) 28 | 29 | def _call(self, tk): 30 | try: 31 | data = _direct.xmlservice(tk.xml_in(), self.ctl, self.ipc) 32 | 33 | if len(data) == 0: 34 | # Older versions of XMLSERVICE did not work correctly when 35 | # called directly from a 64-bit PASE application and output is 36 | # left empty. We check for that here and suggest they install 37 | # the updated version. 38 | raise RuntimeError( 39 | "No data returned from XMLSERVICE. " 40 | "This could be a bug present in XMLSERVICE prior to 2.0.1. " 41 | "Perhaps you need to apply PTF SI70667, SI70668 or SI70669?" 42 | ) 43 | 44 | return data.decode('utf-8') 45 | except NameError: 46 | # When we drop support for Python 2: 47 | # raise RuntimeError("Not supported on this platform") from None 48 | raise RuntimeError("Not supported on this platform") 49 | -------------------------------------------------------------------------------- /src/itoolkit/transport/errors.py: -------------------------------------------------------------------------------- 1 | from ..errors import TransportClosedException 2 | 3 | class TransportClosedError(TransportClosedException): 4 | """Raised when an operation is performed on a closed transport 5 | 6 | .. versionadded:: 1.7.1 7 | """ 8 | # TODO: When TransportClosedException is removed, rebase this class on 9 | # TransportError 10 | pass 11 | 12 | -------------------------------------------------------------------------------- /src/itoolkit/transport/http.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .base import XmlServiceTransport 3 | import contextlib 4 | 5 | from urllib.request import urlopen 6 | from urllib.parse import urlencode 7 | 8 | __all__ = [ 9 | 'HttpTransport' 10 | ] 11 | 12 | 13 | class HttpTransport(XmlServiceTransport): 14 | """Call XMLSERVICE using FastCGI endpoint 15 | 16 | For more information, refer to 17 | http://yips.idevcloud.com/wiki/index.php/XMLService/XMLSERVICEGeneric 18 | 19 | Args: 20 | url (str): XMLSERVICE FastCGI endpoint 21 | eg. https://example.com/cgi-bin/xmlcgi.pgm 22 | user (str): Database user profile name 23 | password (str, optional): Database password 24 | database (str, optional): Database name (RDB) to connect to 25 | **kwargs: Base transport options. See `XmlServiceTransport`. 26 | 27 | Example: 28 | >>> from itoolkit.transport import HttpTransport 29 | >>> endpoint = 'https://example.com/cgi-bin/xmlcgi.pgm' 30 | >>> transport = HttpTransport(endpoint, 'user', 'pass') 31 | """ 32 | def __init__(self, url, user, password, database='*LOCAL', **kwargs): 33 | super().__init__(**kwargs) 34 | self.trace_attrs.extend([ 35 | 'url', 36 | ('uid', 'user'), 37 | ('rdb', 'database'), 38 | ]) 39 | 40 | self.url = url 41 | self.uid = user 42 | self.pwd = password 43 | self.db = database 44 | 45 | OUT_SIZE = 16 * 1000 * 1000 46 | 47 | def _call(self, tk): 48 | data = urlencode({ 49 | 'db2': self.db, 50 | 'uid': self.uid, 51 | 'pwd': self.pwd, 52 | 'ipc': self.ipc, 53 | 'ctl': self.ctl, 54 | 'xmlin': tk.xml_in(), 55 | 'xmlout': self.OUT_SIZE 56 | }).encode('utf-8') 57 | 58 | with contextlib.closing(urlopen(self.url, data)) as f: 59 | return f.read() 60 | -------------------------------------------------------------------------------- /src/itoolkit/transport/ssh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | XMLSERVICE ssh call (ssh job, often in QUSRWRK) 4 | 5 | License: 6 | BSD (LICENSE) 7 | -- or -- 8 | http://yips.idevcloud.com/wiki/index.php/XMLService/LicenseXMLService 9 | 10 | Example: 11 | from itoolkit.transport import SshTransport 12 | import paramiko 13 | ssh = paramiko.SSHClient() 14 | # configure paramiko. Using only WarningPolicy() is not secure 15 | ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) 16 | ssh.connect(host, username="linux", password="linux1") 17 | itransport = SshTransport(ssh) 18 | 19 | Notes: 20 | 1) Always uses XMLSERVICE shipped in the QXMLSERV library 21 | 2) does not disconnect the given SSHClient object when finished 22 | 23 | """ 24 | from .base import XmlServiceTransport 25 | 26 | import socket 27 | 28 | __all__ = [ 29 | 'SshTransport' 30 | ] 31 | 32 | 33 | class SshTransport(XmlServiceTransport): 34 | """ 35 | Transport XMLSERVICE calls over SSH connection. 36 | 37 | Args: 38 | sshclient (paramiko.SSHClient): connected and authenticated connection 39 | 40 | Example: 41 | >>> from itoolkit.transport import SshTransport 42 | >>> import paramiko 43 | >>> ssh = paramiko.SSHClient() 44 | >>> ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) 45 | >>> ssh.connect(host, username="user", password="pass") 46 | >>> transport = SshTransport(ssh) 47 | 48 | .. warning:: 49 | 50 | Using WarningPolicy is shown only as an example and could lead to 51 | security issues. Please refer to the `set_missing_host_key_policy`_ 52 | docs for more info on other policies that may be more appropriate. 53 | 54 | .. _set_missing_host_key_policy: http://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.set_missing_host_key_policy 55 | 56 | Returns: 57 | (obj) 58 | """ # noqa: E501 See https://github.com/PyCQA/pycodestyle/issues/888 59 | 60 | def __init__(self, sshclient=None, **kwargs): 61 | # TODO: allow connection to be materialized from IBM Cloud deployments 62 | if not hasattr(sshclient, "exec_command"): 63 | raise Exception("An instance of paramiko.SSHClient is required") 64 | 65 | super().__init__(**kwargs) 66 | 67 | self.conn = sshclient 68 | 69 | def _call(self, tk): 70 | """Call xmlservice with accumulated input XML. 71 | 72 | Args: 73 | tk - iToolkit object 74 | 75 | Returns: 76 | xml 77 | """ 78 | command = "/QOpenSys/pkgs/bin/xmlservice-cli" 79 | stdin, stdout, stderr = self.conn.exec_command(command) 80 | channel = stdout.channel 81 | 82 | xml_in = tk.xml_in() 83 | stdin.write(xml_in.encode()) 84 | stdin.close() 85 | channel.shutdown_write() 86 | 87 | # Disable blocking I/O 88 | # chan.settimeout(0.0) is equivalent to chan.setblocking(0) 89 | # https://docs.paramiko.org/en/stable/api/channel.html#paramiko.channel.Channel.settimeout 90 | channel.settimeout(0.0) 91 | 92 | # rather than doing all this loop-de-loop, we could instead just use 93 | # a single call to stdout.readlines(), but there is a remote 94 | # possibility that the process is hanging writing to a filled-up 95 | # stderr pipe. So, we read from both until we're all done 96 | err_out = b"" 97 | xml_out = b"" 98 | 99 | # Convenience wrapper for reading data from stdout/stderr 100 | # Returns empty binary string if EOF *or* timeout (no data) 101 | # Closes file on EOF 102 | def read_data(f): 103 | if f.closed: 104 | return b"" 105 | 106 | try: 107 | data = f.read() 108 | if not data: 109 | f.close() 110 | return data 111 | except socket.timeout: 112 | return b"" 113 | 114 | while not all((stdout.closed, stderr.closed)): 115 | xml_out += read_data(stdout) 116 | err_out += read_data(stderr) 117 | 118 | channel.close() 119 | return xml_out 120 | 121 | def _close(self): 122 | self.conn.close() 123 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | from mock import Mock, MagicMock 6 | 7 | 8 | class MockTransport(object): 9 | def __init__(self, out=None): 10 | self._out = out 11 | 12 | def set_out(self, out): 13 | self._out = out 14 | 15 | def call(self, tk): 16 | return self._out 17 | 18 | def trace_data(self): 19 | return "" 20 | 21 | 22 | class MockCursor(object): 23 | def __init__(self): 24 | pass 25 | 26 | def callproc(self, proc, args): 27 | return False 28 | 29 | 30 | class MockDatabase(object): 31 | def __init__(self): 32 | pass 33 | 34 | def cursor(self): 35 | return MockCursor() 36 | 37 | 38 | @pytest.fixture 39 | def transport(): 40 | return MockTransport() 41 | 42 | 43 | XML = "\n" 44 | 45 | 46 | def mock_database(use_callproc): 47 | conn = Mock() 48 | 49 | cursor = MagicMock() 50 | cursor.fetchall.return_value = [(XML,)] 51 | 52 | if use_callproc: 53 | cursor.callproc.return_value = True 54 | del cursor.execute 55 | else: 56 | cursor.execute.return_value = True 57 | del cursor.callproc 58 | 59 | conn.cursor.return_value = cursor 60 | return conn 61 | 62 | 63 | @pytest.fixture 64 | def database_callproc(): 65 | return mock_database(use_callproc=True) 66 | 67 | 68 | @pytest.fixture 69 | def database_execute(): 70 | return mock_database(use_callproc=False) 71 | 72 | 73 | @pytest.fixture 74 | def database_close_exception(): 75 | conn = mock_database(use_callproc=True) 76 | conn.close.side_effect = RuntimeError 77 | return conn 78 | 79 | 80 | @pytest.fixture 81 | def database(): 82 | return mock_database(use_callproc=True) 83 | -------------------------------------------------------------------------------- /tests/test_cmd_error.py: -------------------------------------------------------------------------------- 1 | from itoolkit import iToolKit, iCmd 2 | 3 | 4 | def test_cmd_error(transport): 5 | """Test calling a CL command with an invalid parameter returns an error""" 6 | 7 | transport.set_out( 8 | """*** error CHGLIBL LIBL(FROGLEGLONG) 9 | 202 10 | XML Toolkit 1.9.9 11 | 12 | 202 13 | 1100012 14 | XML run cmd failed 15 | CHGLIBL LIBL(FROGLEGLONG) 16 | 17 | 18 | 202 19 | 1100012 20 | XML run cmd failed 21 | CHGLIBL LIBL(FROGLEGLONG) 22 | 23 | 24 | *na 25 | FFFFFFFF 26 | QSQSRVR 27 | QUSER 28 | 774740 29 | *ACTIVE 30 | KADLER 31 | 37 32 | 37 33 | 0 34 | ENU 35 | US 36 | QSYSWRK 37 | QSYS 38 | 39 | QSYS QSYS2 QHLPSYS QUSRSYS 40 | QGPL QTEMP QDEVELOP QBLDSYS QBLDSYSR 41 | see log scan, not error list 42 | 43 | 44 | """) # noqa E501 45 | 46 | tk = iToolKit() 47 | 48 | tk.add(iCmd('cmderror', 'CHGLIBL LIBL(FROGLEGLONG)')) 49 | 50 | tk.call(transport) 51 | 52 | cmderror = tk.dict_out('cmderror') 53 | 54 | for k, v in cmderror.items(): 55 | if not k.startswith('error'): 56 | continue 57 | 58 | item = cmderror[k] 59 | if 'errnoxml' not in item: 60 | continue 61 | 62 | assert(item['errnoxml'] == '1100012') 63 | -------------------------------------------------------------------------------- /tests/test_cmd_rexx.py: -------------------------------------------------------------------------------- 1 | from itoolkit import iToolKit, iCmd 2 | 3 | _xml = """ 4 | 5 | +++ success RTVJOBA USRLIBL(?) SYSLIBL(?) CCSID(?N) OUTQ(?) 6 | QGPL QTEMP QDEVELOP QBLDSYS QBLDSYSR 7 | QSYS QSYS2 QHLPSYS QUSRSYS 8 | 37 9 | *DEV 10 | 11 | """ # noqa E501 12 | 13 | 14 | def test_cmd_rexx_row(transport): 15 | """Test calling command with output parameters returns data in rows""" 16 | transport.set_out(_xml) 17 | 18 | tk = iToolKit(irow=1) 19 | tk.add(iCmd('rtvjoba', 'RTVJOBA USRLIBL(?) SYSLIBL(?) CCSID(?N) OUTQ(?)')) 20 | 21 | tk.call(transport) 22 | rtvjoba = tk.dict_out('rtvjoba') 23 | 24 | assert('success' in rtvjoba) 25 | assert('row' in rtvjoba) 26 | row = rtvjoba['row'] 27 | 28 | assert(len(row) == 4) 29 | assert('USRLIBL' in row[0]) 30 | assert('SYSLIBL' in row[1]) 31 | assert('CCSID' in row[2]) 32 | assert('OUTQ' in row[3]) 33 | 34 | 35 | def test_cmd_rexx_no_row(transport): 36 | """Test calling command with output parms and irow=0 returns data in dict""" 37 | transport.set_out(_xml) 38 | 39 | tk = iToolKit(irow=0) 40 | tk.add(iCmd('rtvjoba', 'RTVJOBA USRLIBL(?) SYSLIBL(?) CCSID(?N) OUTQ(?)')) 41 | 42 | tk.call(transport) 43 | rtvjoba = tk.dict_out('rtvjoba') 44 | 45 | assert('success' in rtvjoba) 46 | assert('USRLIBL' in rtvjoba) 47 | assert('SYSLIBL' in rtvjoba) 48 | assert('CCSID' in rtvjoba) 49 | assert('OUTQ' in rtvjoba) 50 | -------------------------------------------------------------------------------- /tests/test_deprecated_transports.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_rest_import_ok(): 5 | from itoolkit.rest.irestcall import iRestCall 6 | 7 | with pytest.deprecated_call(): 8 | iRestCall('https://example.com', 'user', 'password') 9 | 10 | 11 | def test_db2_import_ok(database): 12 | from itoolkit.db2.idb2call import iDB2Call 13 | 14 | with pytest.deprecated_call(): 15 | iDB2Call(database) 16 | 17 | 18 | def test_lib_import_ok(): 19 | from itoolkit.lib.ilibcall import iLibCall 20 | 21 | with pytest.deprecated_call(): 22 | iLibCall() 23 | -------------------------------------------------------------------------------- /tests/test_pgm.py: -------------------------------------------------------------------------------- 1 | from itoolkit import iToolKit, iPgm, iDS, iData 2 | 3 | 4 | def test_pgm(transport): 5 | """Test calling ZZCALL 6 | https://bitbucket.org/inext/xmlservice-rpg/src/master/test.rpg/zzcall.rpgle 7 | """ 8 | 9 | transport.set_out(""" 10 | 11 | 12 | 13 | C 14 | 15 | 16 | D 17 | 18 | 19 | 321.1234 20 | 21 | 22 | 1234567890.12 23 | 24 | 25 | 26 | E 27 | F 28 | 333.3330 29 | 4444444444.44 30 | 31 | 32 | +++ success XMLSERVICE ZZCALL 33 | 34 | 35 | """) 36 | 37 | tk = iToolKit() 38 | tk.add( 39 | iPgm('zzcall', 'ZZCALL', {'lib': 'XMLSERVICE'}) 40 | .addParm(iData('INCHARA', '1a', 'a')) 41 | .addParm(iData('INCHARB', '1a', 'b')) 42 | .addParm(iData('INDEC1', '7p4', '32.1234')) 43 | .addParm(iData('INDEC2', '12p2', '33.33')) 44 | .addParm(iDS('INDS1') 45 | .addData(iData('DSCHARA', '1a', 'a')) 46 | .addData(iData('DSCHARB', '1a', 'b')) 47 | .addData(iData('DSDEC1', '7p4', '32.1234')) 48 | .addData(iData('DSDEC2', '12p2', '33.33')) 49 | ) 50 | ) 51 | tk.call(transport) 52 | 53 | zzcall = tk.dict_out('zzcall') 54 | 55 | assert('success' in zzcall) 56 | 57 | assert(zzcall['INCHARA'] == 'C') 58 | assert(zzcall['INCHARB'] == 'D') 59 | assert(zzcall['INDEC1'] == '321.1234') 60 | assert(zzcall['INDEC2'] == '1234567890.12') 61 | assert(zzcall['INDS1']['DSCHARA'] == 'E') 62 | assert(zzcall['INDS1']['DSCHARB'] == 'F') 63 | assert(zzcall['INDS1']['DSDEC1'] == '333.3330') 64 | assert(zzcall['INDS1']['DSDEC2'] == '4444444444.44') 65 | -------------------------------------------------------------------------------- /tests/test_pgm_error.py: -------------------------------------------------------------------------------- 1 | from itoolkit import iToolKit, iPgm 2 | 3 | 4 | def test_pgm_error(transport): 5 | """Test calling program which doesn't exist returns an error""" 6 | 7 | transport.set_out(""" 8 | 9 | 10 | *** error XMLSERVICE ZZCALLNOT 11 | XML Toolkit 1.9.9 12 | 13 | 1100016 14 | XML run pgm failed 15 | 13 | 14 | 15 | 16 | {0} 17 | 18 | 19 | {1} 20 | 21 | 22 | {1} 23 | 24 | 25 | 26 | {0}1 27 | Test 101 28 | 11 29 | 13.42 30 | 31 | 32 | {0}2 33 | Test 102 34 | 12 35 | 26.84 36 | 37 | 38 | {0}3 39 | Test 103 40 | 13 41 | 40.26 42 | 43 | 44 | {0}4 45 | Test 104 46 | 14 47 | 53.68 48 | 49 | 50 | +++ success XMLSERVICE ZZSRV ZZARRAY 51 | 52 | """.format(name, max_return)) 53 | 54 | tk = iToolKit() 55 | tk.add(iSrvPgm('zzarray', 'ZZSRV', 'ZZARRAY', {'lib': 'XMLSERVICE'}) 56 | .addParm(iData('myName', '10a', name)) 57 | .addParm(iData('myMax', '10i0', max_return)) 58 | .addParm(iData('myCount', '10i0', '', {'enddo': 'mycnt'})) 59 | .addRet(iDS('dcRec_t', {'dim': '999', 'dou': 'mycnt'}) 60 | .addData(iData('dcMyName', '10a', '')) 61 | .addData(iData('dcMyJob', '4096a', '')) 62 | .addData(iData('dcMyRank', '10i0', '')) 63 | .addData(iData('dcMyPay', '12p2', '')) 64 | ) 65 | ) 66 | tk.call(transport) 67 | 68 | zzarray = tk.dict_out('zzarray') 69 | assert('success' in zzarray) 70 | 71 | assert(zzarray['myName'] == name) 72 | assert(zzarray['myMax'] == str(max_return)) 73 | assert(zzarray['myCount'] == str(max_return)) 74 | 75 | for i, rec in enumerate(zzarray['dcRec_t'], start=1): 76 | assert(i <= max_return) 77 | 78 | assert(rec['dcMyName'] == name + str(i)) 79 | assert(rec['dcMyJob'] == "Test 10" + str(i)) 80 | assert(int(rec['dcMyRank']) == 10 + i) 81 | assert(float(rec['dcMyPay']) == 13.42 * i) 82 | -------------------------------------------------------------------------------- /tests/test_unit_cmd.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iCmd 4 | 5 | 6 | def test_rexx(): 7 | cmd = 'RTVJOBA USRLIBL(?) SYSLIBL(?)' 8 | key = 'rtvjoba' 9 | 10 | element = ET.fromstring(iCmd(key, cmd).xml_in()) 11 | assert(element.tag == 'cmd') 12 | 13 | assert('exec' in element.attrib) 14 | assert(element.attrib['exec'] == 'rexx') 15 | 16 | assert('error' in element.attrib) 17 | assert(element.attrib['error'] == 'fast') 18 | 19 | assert('var' in element.attrib) 20 | assert(element.attrib['var'] == key) 21 | 22 | assert(element.text == cmd) 23 | 24 | 25 | def test_rexx_error_on(): 26 | cmd = 'RTVJOBA USRLIBL(?) SYSLIBL(?)' 27 | key = 'rtoiu1nqew' 28 | error = 'on' 29 | 30 | element = ET.fromstring(iCmd(key, cmd, {'error': error}).xml_in()) 31 | assert(element.tag == 'cmd') 32 | 33 | assert('exec' in element.attrib) 34 | assert(element.attrib['exec'] == 'rexx') 35 | 36 | assert('error' in element.attrib) 37 | assert(element.attrib['error'] == error) 38 | 39 | assert('var' in element.attrib) 40 | assert(element.attrib['var'] == key) 41 | 42 | assert(element.text == cmd) 43 | 44 | 45 | def test_rexx_error_off(): 46 | cmd = 'RTVJOBA USRLIBL(?) SYSLIBL(?)' 47 | key = 'lkjwernm' 48 | error = 'off' 49 | 50 | element = ET.fromstring(iCmd(key, cmd, {'error': error}).xml_in()) 51 | assert(element.tag == 'cmd') 52 | 53 | assert('exec' in element.attrib) 54 | assert(element.attrib['exec'] == 'rexx') 55 | 56 | assert('error' in element.attrib) 57 | assert(element.attrib['error'] == error) 58 | 59 | assert('var' in element.attrib) 60 | assert(element.attrib['var'] == key) 61 | 62 | assert(element.text == cmd) 63 | 64 | 65 | def test_exec(): 66 | cmd = 'DSPSYSSTS' 67 | key = 'kjwlenrn' 68 | 69 | element = ET.fromstring(iCmd(key, cmd).xml_in()) 70 | assert(element.tag == 'cmd') 71 | 72 | assert('exec' in element.attrib) 73 | assert(element.attrib['exec'] == 'cmd') 74 | 75 | assert('error' in element.attrib) 76 | assert(element.attrib['error'] == 'fast') 77 | 78 | assert('var' in element.attrib) 79 | assert(element.attrib['var'] == key) 80 | 81 | assert(element.text == cmd) 82 | 83 | 84 | def test_exec_on(): 85 | cmd = 'DSPSYSSTS' 86 | key = 'pndsfnwer' 87 | error = 'on' 88 | 89 | element = ET.fromstring(iCmd(key, cmd, {'error': error}).xml_in()) 90 | assert(element.tag == 'cmd') 91 | 92 | assert('exec' in element.attrib) 93 | assert(element.attrib['exec'] == 'cmd') 94 | 95 | assert('error' in element.attrib) 96 | assert(element.attrib['error'] == error) 97 | 98 | assert('var' in element.attrib) 99 | assert(element.attrib['var'] == key) 100 | 101 | assert(element.text == cmd) 102 | 103 | 104 | def test_exec_off(): 105 | cmd = 'DSPSYSSTS' 106 | key = 'oiuewtrlkjgs' 107 | error = 'off' 108 | 109 | element = ET.fromstring(iCmd(key, cmd, {'error': error}).xml_in()) 110 | assert(element.tag == 'cmd') 111 | 112 | assert('exec' in element.attrib) 113 | assert(element.attrib['exec'] == 'cmd') 114 | 115 | assert('error' in element.attrib) 116 | assert(element.attrib['error'] == error) 117 | 118 | assert('var' in element.attrib) 119 | assert(element.attrib['var'] == key) 120 | 121 | assert(element.text == cmd) 122 | -------------------------------------------------------------------------------- /tests/test_unit_cmd5250.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iCmd5250 4 | 5 | 6 | def test_5250(): 7 | cmd = 'WRKACTJOB' 8 | key = 'lljqezl' 9 | 10 | element = ET.fromstring(iCmd5250(key, cmd).xml_in()) 11 | assert(element.tag == 'sh') 12 | 13 | assert('error' in element.attrib) 14 | assert(element.attrib['error'] == 'fast') 15 | 16 | assert('var' in element.attrib) 17 | assert(element.attrib['var'] == key) 18 | 19 | assert(element.text == "/QOpenSys/usr/bin/system WRKACTJOB") 20 | 21 | 22 | def test_5250_error_on(): 23 | cmd = 'WRKACTJOB' 24 | key = 'rtoiu1nqew' 25 | error = 'on' 26 | 27 | element = ET.fromstring(iCmd5250(key, cmd, {'error': error}).xml_in()) 28 | assert(element.tag == 'sh') 29 | 30 | assert('error' in element.attrib) 31 | assert(element.attrib['error'] == error) 32 | 33 | assert('var' in element.attrib) 34 | assert(element.attrib['var'] == key) 35 | 36 | assert(element.text == "/QOpenSys/usr/bin/system WRKACTJOB") 37 | 38 | 39 | def test_5250_error_off(): 40 | cmd = 'WRKACTJOB' 41 | key = 'lkjwernm' 42 | error = 'off' 43 | 44 | element = ET.fromstring(iCmd5250(key, cmd, {'error': error}).xml_in()) 45 | assert(element.tag == 'sh') 46 | 47 | assert('error' in element.attrib) 48 | assert(element.attrib['error'] == error) 49 | 50 | assert('var' in element.attrib) 51 | assert(element.attrib['var'] == key) 52 | 53 | assert(element.text == "/QOpenSys/usr/bin/system WRKACTJOB") 54 | 55 | 56 | def test_5250_row_on(): 57 | cmd = 'WRKACTJOB' 58 | key = 'rtoiu1nqew' 59 | row = 'on' 60 | 61 | element = ET.fromstring(iCmd5250(key, cmd, {'row': row}).xml_in()) 62 | assert(element.tag == 'sh') 63 | 64 | assert('row' in element.attrib) 65 | assert(element.attrib['row'] == row) 66 | 67 | assert('var' in element.attrib) 68 | assert(element.attrib['var'] == key) 69 | 70 | assert(element.text == "/QOpenSys/usr/bin/system WRKACTJOB") 71 | 72 | 73 | def test_5250_row_off(): 74 | cmd = 'WRKACTJOB' 75 | key = 'lkjwernm' 76 | row = 'off' 77 | 78 | element = ET.fromstring(iCmd5250(key, cmd, {'row': row}).xml_in()) 79 | assert(element.tag == 'sh') 80 | 81 | assert('row' in element.attrib) 82 | assert(element.attrib['row'] == row) 83 | 84 | assert('var' in element.attrib) 85 | assert(element.attrib['var'] == key) 86 | 87 | assert(element.text == "/QOpenSys/usr/bin/system WRKACTJOB") 88 | 89 | def test_5250_space(): 90 | cmd = 'WRKACTJOB SBS(*QINTER)' 91 | key = 'lknwqekrn' 92 | 93 | element = ET.fromstring(iCmd5250(key, cmd).xml_in()) 94 | assert(element.tag == 'sh') 95 | 96 | assert('error' in element.attrib) 97 | assert(element.attrib['error'] == 'fast') 98 | 99 | assert('var' in element.attrib) 100 | assert(element.attrib['var'] == key) 101 | 102 | assert(element.text == "/QOpenSys/usr/bin/system 'WRKACTJOB SBS(*QINTER)'") 103 | 104 | 105 | def test_5250_inner_string(): 106 | cmd = "wrklnk '/test/file'" 107 | key = 'znxvlkja' 108 | 109 | element = ET.fromstring(iCmd5250(key, cmd).xml_in()) 110 | assert(element.tag == 'sh') 111 | 112 | assert('error' in element.attrib) 113 | assert(element.attrib['error'] == 'fast') 114 | 115 | assert('var' in element.attrib) 116 | assert(element.attrib['var'] == key) 117 | 118 | assert(element.text == """/QOpenSys/usr/bin/system 'wrklnk '"'"'/test/file'"'"''""") 119 | -------------------------------------------------------------------------------- /tests/test_unit_data.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iData 4 | 5 | 6 | def test_data(): 7 | key = 'lljqezl' 8 | datatype = '10a' 9 | value = 'foo' 10 | 11 | element = ET.fromstring(iData(key, datatype, value).xml_in()) 12 | 13 | assert(element.tag == 'data') 14 | assert(element.attrib['var'] == key) 15 | assert(element.attrib['type'] == datatype) 16 | assert(element.text == value) 17 | 18 | 19 | def test_data_non_str(): 20 | key = 'madsn' 21 | datatype = '10i0' 22 | value = 0 23 | 24 | element = ET.fromstring(iData(key, datatype, value).xml_in()) 25 | 26 | assert(element.tag == 'data') 27 | assert(element.attrib['var'] == key) 28 | assert(element.attrib['type'] == datatype) 29 | assert(element.text == str(value)) 30 | 31 | 32 | def test_data_and_options(): 33 | key = 'oiujgoihs' 34 | datatype = '10a' 35 | value = 'bar' 36 | opts = { 37 | 'dim': 10, 38 | 'varying': 'on', 39 | } 40 | 41 | element = ET.fromstring(iData(key, datatype, value, opts).xml_in()) 42 | assert(element.tag == 'data') 43 | assert(element.attrib['var'] == key) 44 | assert(element.attrib['type'] == datatype) 45 | for k, v in opts.items(): 46 | assert(element.attrib[k] == str(v)) 47 | assert(element.text == value) 48 | 49 | 50 | def test_data_default_value(): 51 | key = 'madsn' 52 | datatype = '10a' 53 | 54 | element = ET.fromstring(iData(key, datatype).xml_in()) 55 | 56 | assert(element.tag == 'data') 57 | assert(element.attrib['var'] == key) 58 | assert(element.attrib['type'] == datatype) 59 | assert(element.text is None) 60 | -------------------------------------------------------------------------------- /tests/test_unit_idb2call.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itoolkit import iToolKit 4 | from itoolkit.db2.idb2call import iDB2Call 5 | 6 | pytestmark = \ 7 | pytest.mark.filterwarnings("ignore:.*iDB2Call.*:DeprecationWarning") 8 | 9 | XMLIN = "\n" 10 | 11 | # iuid=None, ipwd=None, idb2='*LOCAL', ictl='*here *cdata', 12 | # ipc='*na', isiz=None, ilib=None): 13 | 14 | 15 | def test_idb2call_transport_minimal_callproc(database_callproc): 16 | transport = iDB2Call(database_callproc) 17 | tk = iToolKit() 18 | out = transport.call(tk) 19 | 20 | assert isinstance(out, (bytes, str)) 21 | 22 | cursor = database_callproc.cursor() 23 | 24 | cursor.callproc.assert_called_once() 25 | cursor.fetchall.assert_called_once() 26 | 27 | 28 | def test_idb2call_transport_minimal_execute(database_execute): 29 | transport = iDB2Call(database_execute) 30 | tk = iToolKit() 31 | out = transport.call(tk) 32 | 33 | assert isinstance(out, (bytes, str)) 34 | 35 | cursor = database_execute.cursor() 36 | 37 | cursor.execute.assert_called_once() 38 | cursor.fetchall.assert_called_once() 39 | 40 | 41 | def test_idb2call_with_ibm_db(mocker, database_callproc): 42 | 43 | ibm_db = mocker.patch('itoolkit.db2.idb2call.ibm_db', create=True) 44 | Connection = mocker.patch('itoolkit.db2.idb2call.Connection', create=True) # noqa N806 45 | 46 | class MockConn(object): 47 | pass 48 | 49 | ibm_db.IBM_DBConnection = MockConn 50 | Connection.return_value = database_callproc 51 | 52 | conn = MockConn() 53 | transport = iDB2Call(conn) 54 | tk = iToolKit() 55 | out = transport.call(tk) 56 | 57 | assert isinstance(out, (bytes, str)) 58 | 59 | Connection.assert_called_once_with(conn) 60 | 61 | cursor = database_callproc.cursor() 62 | 63 | cursor.callproc.assert_called_once() 64 | cursor.fetchall.assert_called_once() 65 | 66 | 67 | def test_idb2call_with_uid_pwd(mocker, database_callproc): 68 | 69 | ibm_db = mocker.patch('itoolkit.db2.idb2call.ibm_db', create=True) 70 | connect = mocker.patch('itoolkit.db2.idb2call.connect', create=True) 71 | 72 | class MockConn(object): 73 | pass 74 | 75 | ibm_db.IBM_DBConnection = MockConn 76 | connect.return_value = database_callproc 77 | 78 | user = 'myuser' 79 | password = 'mypassword' 80 | 81 | transport = iDB2Call(user, password) 82 | tk = iToolKit() 83 | out = transport.call(tk) 84 | 85 | assert isinstance(out, (bytes, str)) 86 | 87 | kwargs = dict(database='*LOCAL', user=user, password=password) 88 | connect.assert_called_once_with(**kwargs) 89 | 90 | cursor = database_callproc.cursor() 91 | 92 | cursor.callproc.assert_called_once() 93 | cursor.fetchall.assert_called_once() 94 | -------------------------------------------------------------------------------- /tests/test_unit_irestcall.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | 4 | from itoolkit import iToolKit 5 | from itoolkit.rest.irestcall import iRestCall 6 | 7 | from contextlib import contextmanager 8 | if sys.version_info >= (3, 6): 9 | from urllib.parse import parse_qs 10 | else: 11 | from urllib.parse import urlencode 12 | 13 | pytestmark = \ 14 | pytest.mark.filterwarnings("ignore:.*iRestCall.*:DeprecationWarning") 15 | 16 | XMLIN = "" 17 | 18 | 19 | def mock_http_urlopen(mocker): 20 | mock_urlopen = mocker.patch('itoolkit.transport.http.urlopen') 21 | mock_response = mocker.Mock() 22 | mock_response.read.side_effect = (XMLIN.encode('utf-8'),) 23 | mock_urlopen.return_value = mock_response 24 | 25 | return mock_urlopen 26 | 27 | 28 | def assert_urlopen_params_correct(mock_urlopen, url, uid, pwd, db2='*LOCAL', 29 | ipc='*na', ctl='*here *cdata', 30 | xmlout=str(iRestCall.OUT_SIZE)): 31 | # assert_called_once only available in Python 3.6+ 32 | if sys.version_info >= (3, 6): 33 | mock_urlopen.assert_called_once() 34 | 35 | args, kwargs = mock_urlopen.call_args 36 | 37 | assert len(kwargs) == 0 38 | assert len(args) == 2 39 | 40 | assert args[0] == url 41 | 42 | data = {key.decode('utf-8'): value[0].decode('utf-8') for (key, value) 43 | in parse_qs(args[1]).items()} 44 | 45 | exp_data = dict( 46 | uid=uid, 47 | pwd=pwd, 48 | db2=db2, 49 | ipc=ipc, 50 | ctl=ctl, 51 | xmlout=xmlout, 52 | xmlin=XMLIN, 53 | ) 54 | assert data == exp_data 55 | else: 56 | mock_urlopen.assert_called_once_with(url, urlencode({ 57 | 'db2': db2, 58 | 'uid': uid, 59 | 'pwd': pwd, 60 | 'ipc': ipc, 61 | 'ctl': ctl, 62 | 'xmlin': XMLIN, 63 | 'xmlout': int(xmlout) 64 | }).encode("utf-8")) 65 | 66 | 67 | def test_irestcall_transport_minimal(mocker): 68 | mock_urlopen = mock_http_urlopen(mocker) 69 | 70 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 71 | user = 'dummy' 72 | password = 'passw0rd' 73 | 74 | transport = iRestCall(url, user, password) 75 | tk = iToolKit() 76 | out = transport.call(tk) 77 | 78 | assert isinstance(out, (bytes, str)) 79 | 80 | assert_urlopen_params_correct( 81 | mock_urlopen, 82 | url, 83 | uid=user, 84 | pwd=password, 85 | ) 86 | 87 | 88 | def test_irestcall_transport_without_password(mocker, monkeypatch): 89 | mock_urlopen = mock_http_urlopen(mocker) 90 | 91 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 92 | user = 'dummy' 93 | password = 'passw0rd' 94 | 95 | monkeypatch.setenv('PASSWORD', password) 96 | 97 | transport = iRestCall(url, user, password) 98 | tk = iToolKit() 99 | out = transport.call(tk) 100 | 101 | assert isinstance(out, (bytes, str)) 102 | 103 | assert_urlopen_params_correct( 104 | mock_urlopen, 105 | url, 106 | uid=user, 107 | pwd=password, 108 | ) 109 | 110 | 111 | def test_irestcall_transport_with_database(mocker): 112 | mock_urlopen = mock_http_urlopen(mocker) 113 | 114 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 115 | user = 'dummy' 116 | password = 'passw0rd' 117 | database = 'MYDB' 118 | 119 | transport = iRestCall(url, user, password, idb2=database) 120 | tk = iToolKit() 121 | out = transport.call(tk) 122 | 123 | assert isinstance(out, (bytes, str)) 124 | 125 | assert_urlopen_params_correct( 126 | mock_urlopen, 127 | url, 128 | uid=user, 129 | pwd=password, 130 | db2=database 131 | ) 132 | 133 | 134 | def test_irestcall_transport_with_ipc(mocker): 135 | mock_urlopen = mock_http_urlopen(mocker) 136 | 137 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 138 | user = 'dummy' 139 | password = 'passw0rd' 140 | ipc = '/my/ipc/key' 141 | 142 | transport = iRestCall(url, user, password, ipc=ipc) 143 | tk = iToolKit() 144 | out = transport.call(tk) 145 | 146 | assert isinstance(out, (bytes, str)) 147 | 148 | assert_urlopen_params_correct( 149 | mock_urlopen, 150 | url, 151 | uid=user, 152 | pwd=password, 153 | ipc=ipc 154 | ) 155 | 156 | 157 | def test_irestcall_transport_with_ctl(mocker): 158 | mock_urlopen = mock_http_urlopen(mocker) 159 | 160 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 161 | user = 'dummy' 162 | password = 'passw0rd' 163 | ctl = '*sbmjob' 164 | 165 | transport = iRestCall(url, user, password, ictl=ctl) 166 | tk = iToolKit() 167 | out = transport.call(tk) 168 | 169 | assert isinstance(out, (bytes, str)) 170 | 171 | assert_urlopen_params_correct( 172 | mock_urlopen, 173 | url, 174 | uid=user, 175 | pwd=password, 176 | ctl=ctl 177 | ) 178 | 179 | 180 | def test_irestcall_transport_with_size(mocker, recwarn): 181 | mock_urlopen = mock_http_urlopen(mocker) 182 | 183 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 184 | user = 'dummy' 185 | password = 'passw0rd' 186 | size = 10000 187 | 188 | # Since iRestCall returns a deprecation warning already, 189 | # we can't use deprecated_call() to validate that we got 190 | # a deprecation warning for isiz, since deprecated_call 191 | # only validates that at least 1 deprecation warning was 192 | # emitted 193 | @contextmanager 194 | def allow_deprecated(): 195 | import warnings 196 | try: 197 | warnings.simplefilter("always") 198 | yield 199 | finally: 200 | warnings.simplefilter("default") 201 | 202 | with allow_deprecated(): 203 | transport = iRestCall(url, user, password, isiz=size) 204 | tk = iToolKit() 205 | out = transport.call(tk) 206 | 207 | assert isinstance(out, (bytes, str)) 208 | assert len(recwarn) == 2 209 | assert isinstance(recwarn[0].category, type(DeprecationWarning)) 210 | assert isinstance(recwarn[1].category, type(DeprecationWarning)) 211 | 212 | assert_urlopen_params_correct( 213 | mock_urlopen, 214 | url, 215 | uid=user, 216 | pwd=password, 217 | ) 218 | -------------------------------------------------------------------------------- /tests/test_unit_pgm.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iPgm 4 | 5 | 6 | def test_pgm(): 7 | pgm = 'MYPGM' 8 | key = 'lljqezl' 9 | 10 | element = ET.fromstring(iPgm(key, pgm).xml_in()) 11 | assert(element.tag == 'pgm') 12 | 13 | assert('var' in element.attrib) 14 | assert(element.attrib['var'] == key) 15 | 16 | assert('name' in element.attrib) 17 | assert(element.attrib['name'] == pgm) 18 | 19 | assert('error' in element.attrib) 20 | assert(element.attrib['error'] == 'fast') 21 | 22 | 23 | def test_pgm_error_on(): 24 | pgm = 'MYPGM' 25 | key = 'rtoiu1nqew' 26 | error = 'on' 27 | 28 | element = ET.fromstring(iPgm(key, pgm, {'error': error}).xml_in()) 29 | assert(element.tag == 'pgm') 30 | 31 | assert('var' in element.attrib) 32 | assert(element.attrib['var'] == key) 33 | 34 | assert('name' in element.attrib) 35 | assert(element.attrib['name'] == pgm) 36 | 37 | assert('lib' not in element.attrib) 38 | assert('func' not in element.attrib) 39 | 40 | assert('error' in element.attrib) 41 | assert(element.attrib['error'] == error) 42 | 43 | 44 | def test_pgm_error_off(): 45 | pgm = 'MYPGM' 46 | key = 'lkjwernm' 47 | error = 'off' 48 | 49 | element = ET.fromstring(iPgm(key, pgm, {'error': error}).xml_in()) 50 | assert(element.tag == 'pgm') 51 | 52 | assert('var' in element.attrib) 53 | assert(element.attrib['var'] == key) 54 | 55 | assert('name' in element.attrib) 56 | assert(element.attrib['name'] == pgm) 57 | 58 | assert('lib' not in element.attrib) 59 | assert('func' not in element.attrib) 60 | 61 | assert('error' in element.attrib) 62 | assert(element.attrib['error'] == error) 63 | 64 | 65 | def test_pgm_lib(): 66 | pgm = 'MYPGM' 67 | key = 'rtoiu1nqew' 68 | lib = 'MYLIB' 69 | 70 | element = ET.fromstring(iPgm(key, pgm, {'lib': lib}).xml_in()) 71 | assert(element.tag == 'pgm') 72 | 73 | assert('var' in element.attrib) 74 | assert(element.attrib['var'] == key) 75 | 76 | assert('name' in element.attrib) 77 | assert(element.attrib['name'] == pgm) 78 | 79 | assert('lib' in element.attrib) 80 | assert(element.attrib['lib'] == lib) 81 | 82 | assert('func' not in element.attrib) 83 | -------------------------------------------------------------------------------- /tests/test_unit_sh.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSh 4 | 5 | 6 | def test_sh(): 7 | cmd = 'ls -l' 8 | key = 'lljqezl' 9 | 10 | element = ET.fromstring(iSh(key, cmd).xml_in()) 11 | assert(element.tag == 'sh') 12 | 13 | assert('error' in element.attrib) 14 | assert(element.attrib['error'] == 'fast') 15 | 16 | assert('var' in element.attrib) 17 | assert(element.attrib['var'] == key) 18 | 19 | assert(element.text == cmd) 20 | 21 | 22 | def test_sh_error_on(): 23 | cmd = 'ls -l' 24 | key = 'rtoiu1nqew' 25 | error = 'on' 26 | 27 | element = ET.fromstring(iSh(key, cmd, {'error': error}).xml_in()) 28 | assert(element.tag == 'sh') 29 | 30 | assert('error' in element.attrib) 31 | assert(element.attrib['error'] == error) 32 | 33 | assert('var' in element.attrib) 34 | assert(element.attrib['var'] == key) 35 | 36 | assert(element.text == cmd) 37 | 38 | 39 | def test_sh_error_off(): 40 | cmd = 'ls -l' 41 | key = 'lkjwernm' 42 | error = 'off' 43 | 44 | element = ET.fromstring(iSh(key, cmd, {'error': error}).xml_in()) 45 | assert(element.tag == 'sh') 46 | 47 | assert('error' in element.attrib) 48 | assert(element.attrib['error'] == error) 49 | 50 | assert('var' in element.attrib) 51 | assert(element.attrib['var'] == key) 52 | 53 | assert(element.text == cmd) 54 | 55 | 56 | def test_sh_row_on(): 57 | cmd = 'ls -l' 58 | key = 'rtoiu1nqew' 59 | row = 'on' 60 | 61 | element = ET.fromstring(iSh(key, cmd, {'row': row}).xml_in()) 62 | assert(element.tag == 'sh') 63 | 64 | assert('row' in element.attrib) 65 | assert(element.attrib['row'] == row) 66 | 67 | assert('var' in element.attrib) 68 | assert(element.attrib['var'] == key) 69 | 70 | assert(element.text == cmd) 71 | 72 | 73 | def test_sh_row_off(): 74 | cmd = 'ls -l' 75 | key = 'lkjwernm' 76 | row = 'off' 77 | 78 | element = ET.fromstring(iSh(key, cmd, {'row': row}).xml_in()) 79 | assert(element.tag == 'sh') 80 | 81 | assert('row' in element.attrib) 82 | assert(element.attrib['row'] == row) 83 | 84 | assert('var' in element.attrib) 85 | assert(element.attrib['var'] == key) 86 | 87 | assert(element.text == cmd) 88 | -------------------------------------------------------------------------------- /tests/test_unit_sql_execute.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlExecute, iSqlParm 4 | 5 | 6 | def test_sql_execute(): 7 | key = 'khnusoyh' 8 | 9 | element = ET.fromstring(iSqlExecute(key).xml_in()) 10 | assert(element.tag == 'sql') 11 | 12 | assert(len(element.attrib) == 1) 13 | 14 | assert('var' in element.attrib) 15 | assert(element.attrib['var'] == key) 16 | 17 | assert(element.text is None) 18 | 19 | children = tuple(iter(element)) 20 | 21 | assert(len(children) == 1) 22 | element = children[0] 23 | 24 | assert(element.tag == 'execute') 25 | 26 | assert(len(element.attrib) == 2) 27 | 28 | assert('error' in element.attrib) 29 | assert(element.attrib['error'] == 'fast') 30 | 31 | assert('var' in element.attrib) 32 | assert(element.attrib['var'] == key) 33 | 34 | 35 | def test_sql_execute_error_on(): 36 | key = 'zteaevna' 37 | error = 'on' 38 | 39 | element = ET.fromstring(iSqlExecute(key, {'error': error}).xml_in()) 40 | assert(element.tag == 'sql') 41 | 42 | assert(len(element.attrib) == 1) 43 | 44 | assert('var' in element.attrib) 45 | assert(element.attrib['var'] == key) 46 | 47 | assert(element.text is None) 48 | 49 | children = tuple(iter(element)) 50 | 51 | assert(len(children) == 1) 52 | element = children[0] 53 | 54 | assert(element.tag == 'execute') 55 | 56 | assert(len(element.attrib) == 2) 57 | 58 | assert('error' in element.attrib) 59 | assert(element.attrib['error'] == error) 60 | 61 | assert('var' in element.attrib) 62 | assert(element.attrib['var'] == key) 63 | 64 | 65 | def test_sql_execute_error_off(): 66 | key = 'xewyhrda' 67 | error = 'off' 68 | 69 | element = ET.fromstring(iSqlExecute(key, {'error': error}).xml_in()) 70 | assert(element.tag == 'sql') 71 | 72 | assert(len(element.attrib) == 1) 73 | 74 | assert('var' in element.attrib) 75 | assert(element.attrib['var'] == key) 76 | 77 | assert(element.text is None) 78 | 79 | children = tuple(iter(element)) 80 | 81 | assert(len(children) == 1) 82 | element = children[0] 83 | 84 | assert(element.tag == 'execute') 85 | 86 | assert(len(element.attrib) == 2) 87 | 88 | assert('error' in element.attrib) 89 | assert(element.attrib['error'] == error) 90 | 91 | assert('var' in element.attrib) 92 | assert(element.attrib['var'] == key) 93 | 94 | 95 | def test_sql_execute_conn_set(): 96 | key = 'oshcnxve' 97 | conn = 'conn-label' 98 | 99 | element = ET.fromstring(iSqlExecute(key, {'conn': conn}).xml_in()) 100 | assert(element.tag == 'sql') 101 | 102 | assert(len(element.attrib) == 1) 103 | 104 | assert('var' in element.attrib) 105 | assert(element.attrib['var'] == key) 106 | 107 | assert(element.text is None) 108 | 109 | children = tuple(iter(element)) 110 | 111 | assert(len(children) == 1) 112 | element = children[0] 113 | 114 | assert(element.tag == 'execute') 115 | 116 | assert(len(element.attrib) == 3) 117 | 118 | assert('conn' in element.attrib) 119 | assert(element.attrib['conn'] == conn) 120 | 121 | assert('var' in element.attrib) 122 | assert(element.attrib['var'] == key) 123 | 124 | 125 | def test_sql_execute_stmt_set(): 126 | key = 'oftjocui' 127 | stmt = 'stmt-label' 128 | 129 | element = ET.fromstring(iSqlExecute(key, {'stmt': stmt}).xml_in()) 130 | assert(element.tag == 'sql') 131 | 132 | assert(len(element.attrib) == 1) 133 | 134 | assert('var' in element.attrib) 135 | assert(element.attrib['var'] == key) 136 | 137 | assert(element.text is None) 138 | 139 | children = tuple(iter(element)) 140 | 141 | assert(len(children) == 1) 142 | element = children[0] 143 | 144 | assert(element.tag == 'execute') 145 | 146 | assert(len(element.attrib) == 3) 147 | 148 | assert('stmt' in element.attrib) 149 | assert(element.attrib['stmt'] == stmt) 150 | 151 | assert('var' in element.attrib) 152 | assert(element.attrib['var'] == key) 153 | 154 | 155 | def test_sql_execute_options_set(): 156 | key = 'dikeaunc' 157 | options = 'options-label' 158 | 159 | element = ET.fromstring(iSqlExecute(key, {'options': options}).xml_in()) 160 | assert(element.tag == 'sql') 161 | 162 | assert(len(element.attrib) == 1) 163 | 164 | assert('var' in element.attrib) 165 | assert(element.attrib['var'] == key) 166 | 167 | assert(element.text is None) 168 | 169 | children = tuple(iter(element)) 170 | 171 | assert(len(children) == 1) 172 | element = children[0] 173 | 174 | assert(element.tag == 'execute') 175 | 176 | assert(len(element.attrib) == 3) 177 | 178 | assert('options' in element.attrib) 179 | assert(element.attrib['options'] == options) 180 | 181 | assert('var' in element.attrib) 182 | assert(element.attrib['var'] == key) 183 | 184 | 185 | def test_sql_execute_add_one_parm(): 186 | key = 'gibksybk' 187 | parm = 'foo' 188 | 189 | action = iSqlExecute(key) 190 | action.addParm(iSqlParm(parm, parm)) 191 | 192 | element = ET.fromstring(action.xml_in()) 193 | assert(element.tag == 'sql') 194 | 195 | assert(len(element.attrib) == 1) 196 | 197 | assert('var' in element.attrib) 198 | assert(element.attrib['var'] == key) 199 | 200 | assert(element.text is None) 201 | 202 | children = tuple(iter(element)) 203 | 204 | assert(len(children) == 1) 205 | element = children[0] 206 | 207 | assert(element.tag == 'execute') 208 | 209 | assert(len(element.attrib) == 2) 210 | 211 | assert('var' in element.attrib) 212 | assert(element.attrib['var'] == key) 213 | 214 | children = tuple(iter(element)) 215 | assert(len(children) == 1) 216 | element = children[0] 217 | 218 | assert(element.tag == 'parm') 219 | assert(element.text == parm) 220 | 221 | 222 | def test_sql_execute_add_two_parms(): 223 | key = 'yuwhcsed' 224 | parm1 = 'foo' 225 | parm2 = 'bar' 226 | 227 | action = iSqlExecute(key) 228 | action.addParm(iSqlParm(parm1, parm1)) 229 | action.addParm(iSqlParm(parm2, parm2)) 230 | 231 | element = ET.fromstring(action.xml_in()) 232 | assert(element.tag == 'sql') 233 | 234 | assert(len(element.attrib) == 1) 235 | 236 | assert('var' in element.attrib) 237 | assert(element.attrib['var'] == key) 238 | 239 | assert(element.text is None) 240 | 241 | children = tuple(iter(element)) 242 | 243 | assert(len(children) == 1) 244 | element = children[0] 245 | 246 | assert(element.tag == 'execute') 247 | 248 | assert(len(element.attrib) == 2) 249 | 250 | assert('var' in element.attrib) 251 | assert(element.attrib['var'] == key) 252 | 253 | children = tuple(iter(element)) 254 | assert(len(children) == 2) 255 | 256 | element = children[0] 257 | assert(element.tag == 'parm') 258 | assert(element.text == parm1) 259 | 260 | element = children[1] 261 | assert(element.tag == 'parm') 262 | assert(element.text == parm2) 263 | -------------------------------------------------------------------------------- /tests/test_unit_sql_fetch.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlFetch 4 | 5 | 6 | def test_sql_fetch(): 7 | key = 'mulblnxo' 8 | 9 | element = ET.fromstring(iSqlFetch(key).xml_in()) 10 | assert(element.tag == 'sql') 11 | 12 | assert(len(element.attrib) == 1) 13 | 14 | assert('var' in element.attrib) 15 | assert(element.attrib['var'] == key) 16 | 17 | assert(element.text is None) 18 | 19 | children = tuple(iter(element)) 20 | 21 | assert(len(children) == 1) 22 | element = children[0] 23 | 24 | assert(element.tag == 'fetch') 25 | 26 | assert(len(element.attrib) == 3) 27 | 28 | assert('error' in element.attrib) 29 | assert(element.attrib['error'] == 'fast') 30 | 31 | assert('block' in element.attrib) 32 | assert(element.attrib['block'] == 'all') 33 | 34 | assert('var' in element.attrib) 35 | assert(element.attrib['var'] == key) 36 | 37 | 38 | def test_sql_fetch_error_on(): 39 | key = 'opaffdjr' 40 | error = 'on' 41 | 42 | element = ET.fromstring(iSqlFetch(key, {'error': error}).xml_in()) 43 | assert(element.tag == 'sql') 44 | 45 | assert(len(element.attrib) == 1) 46 | 47 | assert('var' in element.attrib) 48 | assert(element.attrib['var'] == key) 49 | 50 | assert(element.text is None) 51 | 52 | children = tuple(iter(element)) 53 | 54 | assert(len(children) == 1) 55 | element = children[0] 56 | 57 | assert(element.tag == 'fetch') 58 | 59 | assert(len(element.attrib) == 3) 60 | 61 | assert('error' in element.attrib) 62 | assert(element.attrib['error'] == error) 63 | 64 | assert('var' in element.attrib) 65 | assert(element.attrib['var'] == key) 66 | 67 | 68 | def test_sql_fetch_error_off(): 69 | key = 'ysdifjyx' 70 | error = 'off' 71 | 72 | element = ET.fromstring(iSqlFetch(key, {'error': error}).xml_in()) 73 | assert(element.tag == 'sql') 74 | 75 | assert(len(element.attrib) == 1) 76 | 77 | assert('var' in element.attrib) 78 | assert(element.attrib['var'] == key) 79 | 80 | assert(element.text is None) 81 | 82 | children = tuple(iter(element)) 83 | 84 | assert(len(children) == 1) 85 | element = children[0] 86 | 87 | assert(element.tag == 'fetch') 88 | 89 | assert(len(element.attrib) == 3) 90 | 91 | assert('error' in element.attrib) 92 | assert(element.attrib['error'] == error) 93 | 94 | assert('var' in element.attrib) 95 | assert(element.attrib['var'] == key) 96 | 97 | 98 | def test_sql_fetch_block_set(): 99 | key = 'ojaxupoq' 100 | block = '10' 101 | 102 | element = ET.fromstring(iSqlFetch(key, {'block': block}).xml_in()) 103 | assert(element.tag == 'sql') 104 | 105 | assert(len(element.attrib) == 1) 106 | 107 | assert('var' in element.attrib) 108 | assert(element.attrib['var'] == key) 109 | 110 | assert(element.text is None) 111 | 112 | children = tuple(iter(element)) 113 | 114 | assert(len(children) == 1) 115 | element = children[0] 116 | 117 | assert(element.tag == 'fetch') 118 | 119 | assert(len(element.attrib) == 3) 120 | 121 | assert('block' in element.attrib) 122 | assert(element.attrib['block'] == block) 123 | 124 | assert('var' in element.attrib) 125 | assert(element.attrib['var'] == key) 126 | 127 | 128 | def test_sql_fetch_desc_on(): 129 | key = 'sefufeoq' 130 | describe = 'on' 131 | 132 | element = ET.fromstring(iSqlFetch(key, {'desc': describe}).xml_in()) 133 | assert(element.tag == 'sql') 134 | 135 | assert(len(element.attrib) == 1) 136 | 137 | assert('var' in element.attrib) 138 | assert(element.attrib['var'] == key) 139 | 140 | assert(element.text is None) 141 | 142 | children = tuple(iter(element)) 143 | 144 | assert(len(children) == 1) 145 | element = children[0] 146 | 147 | assert(element.tag == 'fetch') 148 | 149 | assert(len(element.attrib) == 4) 150 | 151 | assert('desc' in element.attrib) 152 | assert(element.attrib['desc'] == describe) 153 | 154 | assert('var' in element.attrib) 155 | assert(element.attrib['var'] == key) 156 | 157 | 158 | def test_sql_fetch_desc_off(): 159 | key = 'jtucgypy' 160 | describe = 'off' 161 | 162 | element = ET.fromstring(iSqlFetch(key, {'desc': describe}).xml_in()) 163 | assert(element.tag == 'sql') 164 | 165 | assert(len(element.attrib) == 1) 166 | 167 | assert('var' in element.attrib) 168 | assert(element.attrib['var'] == key) 169 | 170 | assert(element.text is None) 171 | 172 | children = tuple(iter(element)) 173 | 174 | assert(len(children) == 1) 175 | element = children[0] 176 | 177 | assert(element.tag == 'fetch') 178 | 179 | assert(len(element.attrib) == 4) 180 | 181 | assert('desc' in element.attrib) 182 | assert(element.attrib['desc'] == describe) 183 | 184 | assert('var' in element.attrib) 185 | assert(element.attrib['var'] == key) 186 | 187 | 188 | def test_sql_fetch_stmt_set(): 189 | key = 'slkgfrav' 190 | stmt = 'stmt-label' 191 | 192 | element = ET.fromstring(iSqlFetch(key, {'stmt': stmt}).xml_in()) 193 | assert(element.tag == 'sql') 194 | 195 | assert(len(element.attrib) == 1) 196 | 197 | assert('var' in element.attrib) 198 | assert(element.attrib['var'] == key) 199 | 200 | assert(element.text is None) 201 | 202 | children = tuple(iter(element)) 203 | 204 | assert(len(children) == 1) 205 | element = children[0] 206 | 207 | assert(element.tag == 'fetch') 208 | 209 | assert(len(element.attrib) == 4) 210 | 211 | assert('stmt' in element.attrib) 212 | assert(element.attrib['stmt'] == stmt) 213 | 214 | assert('var' in element.attrib) 215 | assert(element.attrib['var'] == key) 216 | 217 | 218 | def test_sql_fetch_rec_set(): 219 | key = 'slkgfrav' 220 | records = '10' 221 | 222 | element = ET.fromstring(iSqlFetch(key, {'rec': records}).xml_in()) 223 | assert(element.tag == 'sql') 224 | 225 | assert(len(element.attrib) == 1) 226 | 227 | assert('var' in element.attrib) 228 | assert(element.attrib['var'] == key) 229 | 230 | assert(element.text is None) 231 | 232 | children = tuple(iter(element)) 233 | 234 | assert(len(children) == 1) 235 | element = children[0] 236 | 237 | assert(element.tag == 'fetch') 238 | 239 | assert(len(element.attrib) == 4) 240 | 241 | assert('rec' in element.attrib) 242 | assert(element.attrib['rec'] == records) 243 | 244 | assert('var' in element.attrib) 245 | assert(element.attrib['var'] == key) 246 | -------------------------------------------------------------------------------- /tests/test_unit_sql_free.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlFree 4 | 5 | 6 | def test_sql_free(): 7 | key = 'ifaovjuf' 8 | 9 | element = ET.fromstring(iSqlFree(key).xml_in()) 10 | assert(element.tag == 'sql') 11 | 12 | assert(len(element.attrib) == 1) 13 | 14 | assert('var' in element.attrib) 15 | assert(element.attrib['var'] == key) 16 | 17 | assert(element.text is None) 18 | 19 | children = tuple(iter(element)) 20 | 21 | assert(len(children) == 1) 22 | element = children[0] 23 | 24 | assert(element.tag == 'free') 25 | 26 | assert(len(element.attrib) == 2) 27 | 28 | assert('error' in element.attrib) 29 | assert(element.attrib['error'] == 'fast') 30 | 31 | assert('var' in element.attrib) 32 | assert(element.attrib['var'] == key) 33 | 34 | 35 | def test_sql_free_error_on(): 36 | key = 'nkcfhgwf' 37 | error = 'on' 38 | 39 | element = ET.fromstring(iSqlFree(key, {'error': error}).xml_in()) 40 | assert(element.tag == 'sql') 41 | 42 | assert(len(element.attrib) == 1) 43 | 44 | assert('var' in element.attrib) 45 | assert(element.attrib['var'] == key) 46 | 47 | assert(element.text is None) 48 | 49 | children = tuple(iter(element)) 50 | 51 | assert(len(children) == 1) 52 | element = children[0] 53 | 54 | assert(element.tag == 'free') 55 | 56 | assert(len(element.attrib) == 2) 57 | 58 | assert('error' in element.attrib) 59 | assert(element.attrib['error'] == error) 60 | 61 | assert('var' in element.attrib) 62 | assert(element.attrib['var'] == key) 63 | 64 | 65 | def test_sql_free_error_off(): 66 | key = 'vzumvoan' 67 | error = 'off' 68 | 69 | element = ET.fromstring(iSqlFree(key, {'error': error}).xml_in()) 70 | assert(element.tag == 'sql') 71 | 72 | assert(len(element.attrib) == 1) 73 | 74 | assert('var' in element.attrib) 75 | assert(element.attrib['var'] == key) 76 | 77 | assert(element.text is None) 78 | 79 | children = tuple(iter(element)) 80 | 81 | assert(len(children) == 1) 82 | element = children[0] 83 | 84 | assert(element.tag == 'free') 85 | 86 | assert(len(element.attrib) == 2) 87 | 88 | assert('error' in element.attrib) 89 | assert(element.attrib['error'] == error) 90 | 91 | assert('var' in element.attrib) 92 | assert(element.attrib['var'] == key) 93 | 94 | 95 | def test_sql_free_conn_set(): 96 | key = 'igqywtcq' 97 | conn = 'conn-label' 98 | 99 | element = ET.fromstring(iSqlFree(key, {'conn': conn}).xml_in()) 100 | assert(element.tag == 'sql') 101 | 102 | assert(len(element.attrib) == 1) 103 | 104 | assert('var' in element.attrib) 105 | assert(element.attrib['var'] == key) 106 | 107 | assert(element.text is None) 108 | 109 | children = tuple(iter(element)) 110 | 111 | assert(len(children) == 1) 112 | element = children[0] 113 | 114 | assert(element.tag == 'free') 115 | 116 | assert(len(element.attrib) == 3) 117 | 118 | assert('conn' in element.attrib) 119 | assert(element.attrib['conn'] == conn) 120 | 121 | assert('var' in element.attrib) 122 | assert(element.attrib['var'] == key) 123 | 124 | 125 | def test_sql_free_stmt_set(): 126 | key = 'tofzlwxz' 127 | stmt = 'stmt-label' 128 | 129 | element = ET.fromstring(iSqlFree(key, {'stmt': stmt}).xml_in()) 130 | assert(element.tag == 'sql') 131 | 132 | assert(len(element.attrib) == 1) 133 | 134 | assert('var' in element.attrib) 135 | assert(element.attrib['var'] == key) 136 | 137 | assert(element.text is None) 138 | 139 | children = tuple(iter(element)) 140 | 141 | assert(len(children) == 1) 142 | element = children[0] 143 | 144 | assert(element.tag == 'free') 145 | 146 | assert(len(element.attrib) == 3) 147 | 148 | assert('stmt' in element.attrib) 149 | assert(element.attrib['stmt'] == stmt) 150 | 151 | assert('var' in element.attrib) 152 | assert(element.attrib['var'] == key) 153 | 154 | 155 | def test_sql_free_options_set(): 156 | key = 'poraowkq' 157 | options = 'options-label' 158 | 159 | element = ET.fromstring(iSqlFree(key, {'options': options}).xml_in()) 160 | assert(element.tag == 'sql') 161 | 162 | assert(len(element.attrib) == 1) 163 | 164 | assert('var' in element.attrib) 165 | assert(element.attrib['var'] == key) 166 | 167 | assert(element.text is None) 168 | 169 | children = tuple(iter(element)) 170 | 171 | assert(len(children) == 1) 172 | element = children[0] 173 | 174 | assert(element.tag == 'free') 175 | 176 | assert(len(element.attrib) == 3) 177 | 178 | assert('options' in element.attrib) 179 | assert(element.attrib['options'] == options) 180 | 181 | assert('var' in element.attrib) 182 | assert(element.attrib['var'] == key) 183 | -------------------------------------------------------------------------------- /tests/test_unit_sql_parm.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlParm 4 | 5 | DATA = "Hello, world!" 6 | 7 | 8 | def test_sql_query(): 9 | key = 'dmmyeozq' 10 | 11 | element = ET.fromstring(iSqlParm(key, DATA).xml_in()) 12 | assert(element.tag == 'parm') 13 | assert(element.text == DATA) 14 | 15 | assert(len(element.attrib) == 2) 16 | 17 | assert('io' in element.attrib) 18 | assert(element.attrib['io'] == 'both') 19 | 20 | assert('var' in element.attrib) 21 | assert(element.attrib['var'] == key) 22 | 23 | 24 | def test_sql_prepare_io_in(): 25 | key = 'zbutinao' 26 | io = 'in' 27 | 28 | element = ET.fromstring(iSqlParm(key, DATA, {'io': io}).xml_in()) 29 | assert(element.tag == 'parm') 30 | assert(element.text == DATA) 31 | 32 | assert(len(element.attrib) == 2) 33 | 34 | assert('io' in element.attrib) 35 | assert(element.attrib['io'] == io) 36 | 37 | assert('var' in element.attrib) 38 | assert(element.attrib['var'] == key) 39 | 40 | 41 | def test_sql_prepare_io_out(): 42 | key = 'vqsjrnkv' 43 | io = 'out' 44 | 45 | element = ET.fromstring(iSqlParm(key, DATA, {'io': io}).xml_in()) 46 | assert(element.tag == 'parm') 47 | assert(element.text == DATA) 48 | 49 | assert(len(element.attrib) == 2) 50 | 51 | assert('io' in element.attrib) 52 | assert(element.attrib['io'] == io) 53 | 54 | assert('var' in element.attrib) 55 | assert(element.attrib['var'] == key) 56 | 57 | 58 | def test_sql_execute_conn_set(): 59 | key = 'jrntesje' 60 | conn = 'conn-label' 61 | 62 | element = ET.fromstring(iSqlParm(key, DATA, {'conn': conn}).xml_in()) 63 | assert(element.tag == 'parm') 64 | assert(element.text == DATA) 65 | 66 | assert(len(element.attrib) == 3) 67 | 68 | assert('conn' in element.attrib) 69 | assert(element.attrib['conn'] == conn) 70 | 71 | assert('var' in element.attrib) 72 | assert(element.attrib['var'] == key) 73 | 74 | 75 | def test_sql_execute_stmt_set(): 76 | key = 'miecefin' 77 | stmt = 'stmt-label' 78 | 79 | element = ET.fromstring(iSqlParm(key, DATA, {'stmt': stmt}).xml_in()) 80 | assert(element.tag == 'parm') 81 | assert(element.text == DATA) 82 | 83 | assert(len(element.attrib) == 3) 84 | 85 | assert('stmt' in element.attrib) 86 | assert(element.attrib['stmt'] == stmt) 87 | 88 | assert('var' in element.attrib) 89 | assert(element.attrib['var'] == key) 90 | 91 | 92 | def test_sql_execute_options_set(): 93 | key = 'qeficmfx' 94 | options = 'options-label' 95 | 96 | element = ET.fromstring(iSqlParm(key, DATA, {'options': options}).xml_in()) 97 | assert(element.tag == 'parm') 98 | assert(element.text == DATA) 99 | 100 | assert(len(element.attrib) == 3) 101 | 102 | assert('options' in element.attrib) 103 | assert(element.attrib['options'] == options) 104 | 105 | assert('var' in element.attrib) 106 | assert(element.attrib['var'] == key) 107 | -------------------------------------------------------------------------------- /tests/test_unit_sql_prepare.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlPrepare 4 | 5 | QUERY = "SELECT * FROM QIWS.QCUSTCDT" 6 | 7 | 8 | def test_sql_prepare(): 9 | key = 'mulblnxo' 10 | 11 | element = ET.fromstring(iSqlPrepare(key, QUERY).xml_in()) 12 | assert(element.tag == 'sql') 13 | 14 | assert(len(element.attrib) == 1) 15 | 16 | assert('var' in element.attrib) 17 | assert(element.attrib['var'] == key) 18 | 19 | assert(element.text is None) 20 | 21 | children = tuple(iter(element)) 22 | 23 | assert(len(children) == 1) 24 | element = children[0] 25 | 26 | assert(element.tag == 'prepare') 27 | assert(element.text == QUERY) 28 | 29 | assert(len(element.attrib) == 2) 30 | 31 | assert('error' in element.attrib) 32 | assert(element.attrib['error'] == 'fast') 33 | 34 | assert('var' in element.attrib) 35 | assert(element.attrib['var'] == key) 36 | 37 | 38 | def test_sql_prepare_error_on(): 39 | key = 'opaffdjr' 40 | error = 'on' 41 | 42 | element = ET.fromstring(iSqlPrepare(key, QUERY, {'error': error}).xml_in()) 43 | assert(element.tag == 'sql') 44 | 45 | assert(len(element.attrib) == 1) 46 | 47 | assert('var' in element.attrib) 48 | assert(element.attrib['var'] == key) 49 | 50 | assert(element.text is None) 51 | 52 | children = tuple(iter(element)) 53 | 54 | assert(len(children) == 1) 55 | element = children[0] 56 | 57 | assert(element.tag == 'prepare') 58 | assert(element.text == QUERY) 59 | 60 | assert(len(element.attrib) == 2) 61 | 62 | assert('error' in element.attrib) 63 | assert(element.attrib['error'] == error) 64 | 65 | assert('var' in element.attrib) 66 | assert(element.attrib['var'] == key) 67 | 68 | 69 | def test_sql_prepare_error_off(): 70 | key = 'ysdifjyx' 71 | error = 'off' 72 | 73 | element = ET.fromstring(iSqlPrepare(key, QUERY, {'error': error}).xml_in()) 74 | assert(element.tag == 'sql') 75 | 76 | assert(len(element.attrib) == 1) 77 | 78 | assert('var' in element.attrib) 79 | assert(element.attrib['var'] == key) 80 | 81 | assert(element.text is None) 82 | 83 | children = tuple(iter(element)) 84 | 85 | assert(len(children) == 1) 86 | element = children[0] 87 | 88 | assert(element.tag == 'prepare') 89 | assert(element.text == QUERY) 90 | 91 | assert(len(element.attrib) == 2) 92 | 93 | assert('error' in element.attrib) 94 | assert(element.attrib['error'] == error) 95 | 96 | assert('var' in element.attrib) 97 | assert(element.attrib['var'] == key) 98 | 99 | 100 | def test_sql_execute_conn_set(): 101 | key = 'rjrxwnsq' 102 | conn = 'conn-label' 103 | 104 | element = ET.fromstring(iSqlPrepare(key, QUERY, {'conn': conn}).xml_in()) 105 | assert(element.tag == 'sql') 106 | 107 | assert(len(element.attrib) == 1) 108 | 109 | assert('var' in element.attrib) 110 | assert(element.attrib['var'] == key) 111 | 112 | assert(element.text is None) 113 | 114 | children = tuple(iter(element)) 115 | 116 | assert(len(children) == 1) 117 | element = children[0] 118 | 119 | assert(element.tag == 'prepare') 120 | assert(element.text == QUERY) 121 | 122 | assert(len(element.attrib) == 3) 123 | 124 | assert('conn' in element.attrib) 125 | assert(element.attrib['conn'] == conn) 126 | 127 | assert('var' in element.attrib) 128 | assert(element.attrib['var'] == key) 129 | 130 | 131 | def test_sql_execute_stmt_set(): 132 | key = 'pivywaqm' 133 | stmt = 'stmt-label' 134 | 135 | element = ET.fromstring(iSqlPrepare(key, QUERY, {'stmt': stmt}).xml_in()) 136 | assert(element.tag == 'sql') 137 | 138 | assert(len(element.attrib) == 1) 139 | 140 | assert('var' in element.attrib) 141 | assert(element.attrib['var'] == key) 142 | 143 | assert(element.text is None) 144 | 145 | children = tuple(iter(element)) 146 | 147 | assert(len(children) == 1) 148 | element = children[0] 149 | 150 | assert(element.tag == 'prepare') 151 | assert(element.text == QUERY) 152 | 153 | assert(len(element.attrib) == 3) 154 | 155 | assert('stmt' in element.attrib) 156 | assert(element.attrib['stmt'] == stmt) 157 | 158 | assert('var' in element.attrib) 159 | assert(element.attrib['var'] == key) 160 | 161 | 162 | def test_sql_execute_options_set(): 163 | key = 'ktmqazzp' 164 | options = 'options-label' 165 | 166 | element = ET.fromstring(iSqlPrepare(key, QUERY, 167 | {'options': options}).xml_in()) 168 | assert(element.tag == 'sql') 169 | 170 | assert(len(element.attrib) == 1) 171 | 172 | assert('var' in element.attrib) 173 | assert(element.attrib['var'] == key) 174 | 175 | assert(element.text is None) 176 | 177 | children = tuple(iter(element)) 178 | 179 | assert(len(children) == 1) 180 | element = children[0] 181 | 182 | assert(element.tag == 'prepare') 183 | assert(element.text == QUERY) 184 | 185 | assert(len(element.attrib) == 3) 186 | 187 | assert('options' in element.attrib) 188 | assert(element.attrib['options'] == options) 189 | 190 | assert('var' in element.attrib) 191 | assert(element.attrib['var'] == key) 192 | -------------------------------------------------------------------------------- /tests/test_unit_sql_query.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSqlQuery 4 | 5 | QUERY = "SELECT * FROM QIWS.QCUSTCDT" 6 | 7 | 8 | def test_sql_query(): 9 | key = 'dmmyeozq' 10 | 11 | element = ET.fromstring(iSqlQuery(key, QUERY).xml_in()) 12 | assert(element.tag == 'sql') 13 | 14 | assert(len(element.attrib) == 1) 15 | 16 | assert('var' in element.attrib) 17 | assert(element.attrib['var'] == key) 18 | 19 | assert(element.text is None) 20 | 21 | children = tuple(iter(element)) 22 | 23 | assert(len(children) == 1) 24 | element = children[0] 25 | 26 | assert(element.tag == 'query') 27 | assert(element.text == QUERY) 28 | 29 | assert(len(element.attrib) == 2) 30 | 31 | assert('error' in element.attrib) 32 | assert(element.attrib['error'] == 'fast') 33 | 34 | assert('var' in element.attrib) 35 | assert(element.attrib['var'] == key) 36 | 37 | 38 | def test_sql_prepare_error_on(): 39 | key = 'zbutinao' 40 | error = 'on' 41 | 42 | element = ET.fromstring(iSqlQuery(key, QUERY, {'error': error}).xml_in()) 43 | assert(element.tag == 'sql') 44 | 45 | assert(len(element.attrib) == 1) 46 | 47 | assert('var' in element.attrib) 48 | assert(element.attrib['var'] == key) 49 | 50 | assert(element.text is None) 51 | 52 | children = tuple(iter(element)) 53 | 54 | assert(len(children) == 1) 55 | element = children[0] 56 | 57 | assert(element.tag == 'query') 58 | assert(element.text == QUERY) 59 | 60 | assert(len(element.attrib) == 2) 61 | 62 | assert('error' in element.attrib) 63 | assert(element.attrib['error'] == error) 64 | 65 | assert('var' in element.attrib) 66 | assert(element.attrib['var'] == key) 67 | 68 | 69 | def test_sql_prepare_error_off(): 70 | key = 'vqsjrnkv' 71 | error = 'off' 72 | 73 | element = ET.fromstring(iSqlQuery(key, QUERY, {'error': error}).xml_in()) 74 | assert(element.tag == 'sql') 75 | 76 | assert(len(element.attrib) == 1) 77 | 78 | assert('var' in element.attrib) 79 | assert(element.attrib['var'] == key) 80 | 81 | assert(element.text is None) 82 | 83 | children = tuple(iter(element)) 84 | 85 | assert(len(children) == 1) 86 | element = children[0] 87 | 88 | assert(element.tag == 'query') 89 | assert(element.text == QUERY) 90 | 91 | assert(len(element.attrib) == 2) 92 | 93 | assert('error' in element.attrib) 94 | assert(element.attrib['error'] == error) 95 | 96 | assert('var' in element.attrib) 97 | assert(element.attrib['var'] == key) 98 | 99 | 100 | def test_sql_execute_conn_set(): 101 | key = 'jrntesje' 102 | conn = 'conn-label' 103 | 104 | element = ET.fromstring(iSqlQuery(key, QUERY, {'conn': conn}).xml_in()) 105 | assert(element.tag == 'sql') 106 | 107 | assert(len(element.attrib) == 1) 108 | 109 | assert('var' in element.attrib) 110 | assert(element.attrib['var'] == key) 111 | 112 | assert(element.text is None) 113 | 114 | children = tuple(iter(element)) 115 | 116 | assert(len(children) == 1) 117 | element = children[0] 118 | 119 | assert(element.tag == 'query') 120 | assert(element.text == QUERY) 121 | 122 | assert(len(element.attrib) == 3) 123 | 124 | assert('conn' in element.attrib) 125 | assert(element.attrib['conn'] == conn) 126 | 127 | assert('var' in element.attrib) 128 | assert(element.attrib['var'] == key) 129 | 130 | 131 | def test_sql_execute_stmt_set(): 132 | key = 'miecefin' 133 | stmt = 'stmt-label' 134 | 135 | element = ET.fromstring(iSqlQuery(key, QUERY, {'stmt': stmt}).xml_in()) 136 | assert(element.tag == 'sql') 137 | 138 | assert(len(element.attrib) == 1) 139 | 140 | assert('var' in element.attrib) 141 | assert(element.attrib['var'] == key) 142 | 143 | assert(element.text is None) 144 | 145 | children = tuple(iter(element)) 146 | 147 | assert(len(children) == 1) 148 | element = children[0] 149 | 150 | assert(element.tag == 'query') 151 | assert(element.text == QUERY) 152 | 153 | assert(len(element.attrib) == 3) 154 | 155 | assert('stmt' in element.attrib) 156 | assert(element.attrib['stmt'] == stmt) 157 | 158 | assert('var' in element.attrib) 159 | assert(element.attrib['var'] == key) 160 | 161 | 162 | def test_sql_execute_options_set(): 163 | key = 'qeficmfx' 164 | options = 'options-label' 165 | 166 | element = ET.fromstring(iSqlQuery(key, QUERY, 167 | {'options': options}).xml_in()) 168 | assert(element.tag == 'sql') 169 | 170 | assert(len(element.attrib) == 1) 171 | 172 | assert('var' in element.attrib) 173 | assert(element.attrib['var'] == key) 174 | 175 | assert(element.text is None) 176 | 177 | children = tuple(iter(element)) 178 | 179 | assert(len(children) == 1) 180 | element = children[0] 181 | 182 | assert(element.tag == 'query') 183 | assert(element.text == QUERY) 184 | 185 | assert(len(element.attrib) == 3) 186 | 187 | assert('options' in element.attrib) 188 | assert(element.attrib['options'] == options) 189 | 190 | assert('var' in element.attrib) 191 | assert(element.attrib['var'] == key) 192 | -------------------------------------------------------------------------------- /tests/test_unit_srvpgm.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from itoolkit import iSrvPgm 4 | 5 | 6 | def test_pgm(): 7 | key = 'lljqezl' 8 | pgm = 'MYPGM' 9 | func = 'printf' 10 | 11 | element = ET.fromstring(iSrvPgm(key, pgm, func).xml_in()) 12 | assert(element.tag == 'pgm') 13 | 14 | assert('var' in element.attrib) 15 | assert(element.attrib['var'] == key) 16 | 17 | assert('name' in element.attrib) 18 | assert(element.attrib['name'] == pgm) 19 | 20 | assert('func' in element.attrib) 21 | assert(element.attrib['func'] == func) 22 | 23 | assert('error' in element.attrib) 24 | assert(element.attrib['error'] == 'fast') 25 | 26 | 27 | def test_pgm_error_on(): 28 | key = 'rtoiu1nqew' 29 | pgm = 'MYPGM' 30 | func = 'printf' 31 | error = 'on' 32 | 33 | element = ET.fromstring(iSrvPgm(key, pgm, func, {'error': error}).xml_in()) 34 | assert(element.tag == 'pgm') 35 | 36 | assert('var' in element.attrib) 37 | assert(element.attrib['var'] == key) 38 | 39 | assert('name' in element.attrib) 40 | assert(element.attrib['name'] == pgm) 41 | 42 | assert('func' in element.attrib) 43 | assert(element.attrib['func'] == func) 44 | 45 | assert('lib' not in element.attrib) 46 | 47 | assert('error' in element.attrib) 48 | assert(element.attrib['error'] == error) 49 | 50 | 51 | def test_pgm_error_off(): 52 | key = 'lkjwernm' 53 | pgm = 'MYPGM' 54 | func = 'printf' 55 | error = 'off' 56 | 57 | element = ET.fromstring(iSrvPgm(key, pgm, func, {'error': error}).xml_in()) 58 | assert(element.tag == 'pgm') 59 | 60 | assert('var' in element.attrib) 61 | assert(element.attrib['var'] == key) 62 | 63 | assert('name' in element.attrib) 64 | assert(element.attrib['name'] == pgm) 65 | 66 | assert('func' in element.attrib) 67 | assert(element.attrib['func'] == func) 68 | 69 | assert('lib' not in element.attrib) 70 | 71 | assert('error' in element.attrib) 72 | assert(element.attrib['error'] == error) 73 | 74 | 75 | def test_pgm_lib(): 76 | key = 'rtoiu1nqew' 77 | pgm = 'MYPGM' 78 | func = 'printf' 79 | lib = 'MYLIB' 80 | 81 | element = ET.fromstring(iSrvPgm(key, pgm, func, {'lib': lib}).xml_in()) 82 | assert(element.tag == 'pgm') 83 | 84 | assert('var' in element.attrib) 85 | assert(element.attrib['var'] == key) 86 | 87 | assert('name' in element.attrib) 88 | assert(element.attrib['name'] == pgm) 89 | 90 | assert('func' in element.attrib) 91 | assert(element.attrib['func'] == func) 92 | 93 | assert('lib' in element.attrib) 94 | assert(element.attrib['lib'] == lib) 95 | -------------------------------------------------------------------------------- /tests/test_unit_transport_database.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itoolkit import iToolKit 4 | from itoolkit.transport import DatabaseTransport, TransportClosedError 5 | 6 | def test_database_transport_callproc(database_callproc): 7 | transport = DatabaseTransport(database_callproc) 8 | tk = iToolKit() 9 | out = transport.call(tk) 10 | 11 | assert isinstance(out, (bytes, str)) 12 | 13 | cursor = database_callproc.cursor() 14 | 15 | cursor.callproc.assert_called_once() 16 | cursor.fetchall.assert_called_once() 17 | 18 | 19 | def test_database_transport_execute(database_execute): 20 | transport = DatabaseTransport(database_execute) 21 | tk = iToolKit() 22 | out = transport.call(tk) 23 | 24 | assert isinstance(out, (bytes, str)) 25 | 26 | cursor = database_execute.cursor() 27 | 28 | cursor.execute.assert_called_once() 29 | cursor.fetchall.assert_called_once() 30 | 31 | 32 | def test_database_transport_execute_schema(database_execute): 33 | schema = 'MYSCHEMA' 34 | transport = DatabaseTransport(database_execute, schema=schema) 35 | tk = iToolKit() 36 | out = transport.call(tk) 37 | 38 | assert isinstance(out, (bytes, str)) 39 | 40 | cursor = database_execute.cursor() 41 | 42 | cursor.execute.assert_called_once() 43 | cursor.fetchall.assert_called_once() 44 | 45 | assert len(cursor.execute.call_args[0]) > 0 46 | assert schema in cursor.execute.call_args[0][0] 47 | 48 | 49 | def test_database_transport_callproc_schema(database_execute): 50 | schema = 'MYSCHEMA' 51 | transport = DatabaseTransport(database_execute, schema=schema) 52 | tk = iToolKit() 53 | out = transport.call(tk) 54 | 55 | assert isinstance(out, (bytes, str)) 56 | 57 | cursor = database_execute.cursor() 58 | 59 | cursor.execute.assert_called_once() 60 | cursor.fetchall.assert_called_once() 61 | 62 | assert len(cursor.execute.call_args[0]) > 0 63 | assert schema in cursor.execute.call_args[0][0] 64 | 65 | 66 | def test_database_transport_call_raises_when_closed(database_execute): 67 | schema = 'MYSCHEMA' 68 | transport = DatabaseTransport(database_execute, schema=schema) 69 | transport.close() 70 | 71 | with pytest.raises(TransportClosedError): 72 | tk = iToolKit() 73 | out = transport.call(tk) 74 | 75 | 76 | def test_database_transport_logs_exception_on_close(database_close_exception, caplog, capsys): 77 | schema = 'MYSCHEMA' 78 | transport = DatabaseTransport(database_close_exception, schema=schema) 79 | 80 | tk = iToolKit() 81 | out = transport.call(tk) 82 | 83 | with capsys.disabled(): 84 | transport.close() 85 | 86 | assert len(caplog.records) == 1 87 | l = caplog.records[0] 88 | assert l.levelname == "ERROR" 89 | assert 'Unexpected exception' in l.message 90 | 91 | caplog.clear() 92 | -------------------------------------------------------------------------------- /tests/test_unit_transport_direct.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | 4 | from itoolkit import iToolKit 5 | from itoolkit.transport import DirectTransport, TransportClosedError 6 | import itoolkit.transport.direct 7 | 8 | XMLIN = "" 9 | 10 | 11 | def mock_direct(mocker): 12 | "Mock transport.direct" 13 | mock_direct = mocker.patch('itoolkit.transport.direct._direct', create=True) 14 | 15 | mock_xmlsevice = mocker.Mock() 16 | mock_xmlsevice.return_value = XMLIN.encode('utf-8') 17 | 18 | mock_direct.xmlservice = mock_xmlsevice 19 | 20 | return mock_direct 21 | 22 | 23 | def assert_xmlservice_params_correct(mock_direct, ipc='*na', 24 | ctl='*here *cdata'): 25 | mock_xmlservice = mock_direct.xmlservice 26 | 27 | # assert_called_once only available in Python 3.6+ 28 | if sys.version_info >= (3, 6): 29 | mock_xmlservice.assert_called_once() 30 | 31 | args, kwargs = mock_xmlservice.call_args 32 | 33 | assert len(kwargs) == 0 34 | assert len(args) == 3 35 | 36 | assert args[0] == XMLIN 37 | assert args[1] == ctl 38 | assert args[2] == ipc 39 | else: 40 | mock_xmlservice.assert_called_once_with(xml, ctl, ipc) 41 | 42 | 43 | def test_direct_transport_unsupported(mocker): 44 | "Test that we get an error running on an unsupported platform" 45 | 46 | mock_direct = mocker.patch('itoolkit.transport.direct._direct', create=True) 47 | mock_direct.xmlservice = mocker.Mock(side_effect=NameError) 48 | 49 | transport = DirectTransport() 50 | tk = iToolKit() 51 | with pytest.raises(RuntimeError): 52 | transport.call(tk) 53 | 54 | 55 | def test_direct_transport(mocker): 56 | mock = mock_direct(mocker) 57 | 58 | transport = DirectTransport() 59 | tk = iToolKit() 60 | out = transport.call(tk) 61 | 62 | assert_xmlservice_params_correct(mock) 63 | assert isinstance(out, (bytes, str)) 64 | 65 | 66 | def test_direct_transport_call_raises_when_closed(mocker): 67 | mock = mock_direct(mocker) 68 | 69 | transport = DirectTransport() 70 | transport.close() 71 | 72 | with pytest.raises(TransportClosedError): 73 | tk = iToolKit() 74 | out = transport.call(tk) 75 | -------------------------------------------------------------------------------- /tests/test_unit_transport_http.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | 4 | from itoolkit import iToolKit 5 | from itoolkit.transport import HttpTransport, TransportClosedError 6 | 7 | if sys.version_info >= (3, 6): 8 | from urllib.parse import parse_qs 9 | else: 10 | from urllib.parse import urlencode 11 | 12 | XMLIN = "" 13 | 14 | 15 | def mock_http_urlopen(mocker): 16 | mock_urlopen = mocker.patch('itoolkit.transport.http.urlopen') 17 | mock_response = mocker.Mock() 18 | mock_response.read.side_effect = (XMLIN.encode('utf-8'), ) 19 | mock_urlopen.return_value = mock_response 20 | 21 | return mock_urlopen 22 | 23 | 24 | def assert_urlopen_params_correct(mock_urlopen, url, uid, pwd, db2='*LOCAL', 25 | ipc='*na', ctl='*here *cdata', 26 | xmlout=str(HttpTransport.OUT_SIZE)): 27 | # assert_called_once only available in Python 3.6+ 28 | if sys.version_info >= (3, 6): 29 | mock_urlopen.assert_called_once() 30 | 31 | args, kwargs = mock_urlopen.call_args 32 | 33 | assert len(kwargs) == 0 34 | assert len(args) == 2 35 | 36 | assert args[0] == url 37 | 38 | data = {key.decode('utf-8'): value[0].decode('utf-8') for (key, value) 39 | in parse_qs(args[1]).items()} 40 | 41 | exp_data = dict( 42 | uid=uid, 43 | pwd=pwd, 44 | db2=db2, 45 | ipc=ipc, 46 | ctl=ctl, 47 | xmlout=xmlout, 48 | xmlin=XMLIN, 49 | ) 50 | assert data == exp_data 51 | else: 52 | mock_urlopen.assert_called_once_with(url, urlencode({ 53 | 'db2': db2, 54 | 'uid': uid, 55 | 'pwd': pwd, 56 | 'ipc': ipc, 57 | 'ctl': ctl, 58 | 'xmlin': XMLIN + "\n", 59 | 'xmlout': int(xmlout) 60 | }).encode("utf-8")) 61 | 62 | 63 | def test_http_transport_minimal(mocker): 64 | mock_urlopen = mock_http_urlopen(mocker) 65 | 66 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 67 | user = 'dummy' 68 | password = 'passw0rd' 69 | 70 | transport = HttpTransport(url, user, password) 71 | tk = iToolKit() 72 | out = transport.call(tk) 73 | 74 | assert isinstance(out, (bytes, str)) 75 | 76 | assert_urlopen_params_correct( 77 | mock_urlopen, 78 | url, 79 | uid=user, 80 | pwd=password, 81 | ) 82 | 83 | 84 | def test_http_transport_with_database(mocker): 85 | mock_urlopen = mock_http_urlopen(mocker) 86 | 87 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 88 | user = 'dummy' 89 | password = 'passw0rd' 90 | database = 'MYDB' 91 | 92 | transport = HttpTransport(url, user, password, database=database) 93 | tk = iToolKit() 94 | out = transport.call(tk) 95 | 96 | assert isinstance(out, (bytes, str)) 97 | 98 | assert_urlopen_params_correct( 99 | mock_urlopen, 100 | url, 101 | uid=user, 102 | pwd=password, 103 | db2=database 104 | ) 105 | 106 | 107 | def test_http_transport_call_raises_when_closed(mocker): 108 | mock_urlopen = mock_http_urlopen(mocker) 109 | 110 | url = 'http://example.com/cgi-bin/xmlcgi.pgm' 111 | user = 'dummy' 112 | password = 'passw0rd' 113 | database = 'MYDB' 114 | 115 | transport = HttpTransport(url, user, password, database=database) 116 | transport.close() 117 | 118 | with pytest.raises(TransportClosedError): 119 | tk = iToolKit() 120 | out = transport.call(tk) 121 | -------------------------------------------------------------------------------- /tests/test_unit_transport_ssh.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itoolkit import iToolKit 4 | from itoolkit.transport import SshTransport, TransportClosedError 5 | 6 | 7 | def mock_ssh(mocker): 8 | client = mocker.MagicMock() 9 | 10 | channels = ( 11 | mocker.MagicMock(), 12 | mocker.MagicMock(closed=True), 13 | mocker.MagicMock(closed=True), 14 | ) 15 | 16 | client.channels = channels 17 | 18 | client.exec_command.return_value = channels 19 | 20 | return client 21 | 22 | 23 | def test_ssh_transport_minimal(mocker): 24 | ssh_client = mock_ssh(mocker) 25 | 26 | transport = SshTransport(ssh_client) 27 | tk = iToolKit() 28 | out = transport.call(tk) 29 | 30 | assert isinstance(out, (bytes, str)) 31 | 32 | command = "/QOpenSys/pkgs/bin/xmlservice-cli" 33 | ssh_client.exec_command.assert_called_once() 34 | 35 | args = ssh_client.exec_command.call_args 36 | assert args[0] == (command,) 37 | 38 | 39 | def test_ssh_transport_raises_when_closed(mocker): 40 | ssh_client = mock_ssh(mocker) 41 | 42 | transport = SshTransport(ssh_client) 43 | transport.close() 44 | 45 | with pytest.raises(TransportClosedError): 46 | tk = iToolKit() 47 | out = transport.call(tk) 48 | --------------------------------------------------------------------------------