├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── dependabot.yml └── workflows │ └── CI.yml ├── .gitignore ├── .pre-commit-config.yaml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── .gitignore ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── pyproject.toml ├── setup.cfg ├── tests ├── cassettes │ ├── TestAPI.test_groups.yaml │ ├── TestAPI.test_pagination.yaml │ ├── TestAPI.test_tickets.yaml │ └── TestAPI.test_users.yaml ├── conftest.py └── test_zammad_py.py ├── tox.ini └── zammad_py ├── __init__.py ├── api.py └── exceptions.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Zammad API Client version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master, develop] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4.2.2 13 | - uses: actions/setup-python@v5.6.0 14 | - uses: pre-commit/action@v3.0.1 15 | env: 16 | SKIP: mypy 17 | 18 | tests: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 23 | 24 | steps: 25 | - uses: actions/checkout@v4.2.2 26 | 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5.6.0 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | 32 | - name: Bootstrap poetry 33 | shell: bash 34 | run: curl -sSL https://install.python-poetry.org | python3 - 35 | 36 | - name: Configure poetry 37 | shell: bash 38 | run: poetry config virtualenvs.in-project true 39 | 40 | - name: Install dependencies 41 | shell: bash 42 | run: poetry install 43 | 44 | - name: Run pytest 45 | shell: bash 46 | run: poetry run pytest 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | .idea/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | poetry.lock 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | _readthedocs/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.1.0 4 | hooks: 5 | - id: black 6 | 7 | - repo: https://github.com/PyCQA/flake8 8 | rev: 6.0.0 9 | hooks: 10 | - id: flake8 11 | additional_dependencies: 12 | - flake8-bugbear 13 | - flake8-comprehensions 14 | - flake8-simplify 15 | - pep8-naming 16 | 17 | - repo: https://github.com/pycqa/isort 18 | rev: 5.12.0 19 | hooks: 20 | - id: isort 21 | 22 | - repo: https://github.com/asottile/pyupgrade 23 | rev: v2.31.0 24 | hooks: 25 | - id: pyupgrade 26 | args: 27 | - --py36-plus 28 | 29 | - repo: https://github.com/pre-commit/mirrors-mypy 30 | rev: v0.931 31 | hooks: 32 | - id: mypy 33 | pass_filenames: false 34 | additional_dependencies: 35 | - types-requests 36 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Joe Paul 9 | 10 | Contributors 11 | ------------ 12 | 13 | Matthew Frost 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every 8 | little bit helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/joeirimpan/zammad_py/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" 30 | and "help wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | The Zammad API Client could always use more documentation, whether as part of the 42 | official Zammad API Client docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | To help with documentation, look in the `docs` folder. If you want to generate the documentation locally to see how it looks, you need to install sphinx 46 | 47 | 1. Assuming you installed the dependencies with `poetry install`, you can run to generate the html files. 48 | $ python3 -m sphinx -T -E -b html -d docs/_build/doctrees -D language=en docs/ ./_readthedocs/html 49 | 50 | 2. Navigate to the new _readthedocs/html folder and open index.html with a Browser to see a preview of the documentation. 51 | 52 | Submit Feedback 53 | ~~~~~~~~~~~~~~~ 54 | 55 | The best way to send feedback is to file an issue at https://github.com/joeirimpan/zammad_py/issues. 56 | 57 | If you are proposing a feature: 58 | 59 | * Explain in detail how it would work. 60 | * Keep the scope as narrow as possible, to make it easier to implement. 61 | * Remember that this is a volunteer-driven project, and that contributions 62 | are welcome :) 63 | 64 | Get Started! 65 | ------------ 66 | 67 | Ready to contribute? Here's how to set up `zammad_py` for local development. 68 | 69 | 1. Install poetry 70 | 71 | $ pip3 install poetry flask8 sphinx 72 | 73 | 2. Fork the `zammad_py` repo on GitHub. 74 | 75 | 3. Clone your fork locally:: 76 | 77 | $ git clone git@github.com:your_name_here/zammad_py.git 78 | 79 | 4. This project uses Poetry (https://python-poetry.org/). Set up the projects dependencies like this: 80 | 81 | $ pip3 install poetry 82 | $ cd zammad_py/ 83 | $ poetry install 84 | 85 | Poetry will create a virtual environment under your home directory. You can see if that worked by running: 86 | 87 | $ ls ~/.cache/pypoetry/virtualenvs/zammad-py* 88 | 89 | Then activate the environment like this: 90 | 91 | $ ~/.cache/pypoetry/virtualenvs/zammad-py*/bin/activate 92 | 93 | 5. Create a branch for local development:: 94 | 95 | $ git checkout -b name-of-your-bugfix-or-feature 96 | 97 | Now you can make your changes locally. 98 | 99 | 6. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 100 | 101 | $ flake8 zammad_py tests 102 | $ py.test 103 | $ tox 104 | 105 | To get flake8 and tox, just pip install them into your virtualenv. 106 | 107 | 7. Commit your changes and push your branch to GitHub:: 108 | 109 | $ git add . 110 | $ git commit -m "Your detailed description of your changes." 111 | $ git push origin name-of-your-bugfix-or-feature 112 | 113 | 8. Submit a pull request through the GitHub website. 114 | 115 | Pull Request Guidelines 116 | ----------------------- 117 | 118 | Before you submit a pull request, check that it meets these guidelines: 119 | 120 | 1. The pull request should include tests. 121 | 2. If the pull request adds functionality, the docs should be updated. Put 122 | your new functionality into a function with a docstring, and add the 123 | feature to the list in README.rst. 124 | 3. The pull request should work for Python >=3.8.1 125 | 126 | Tips 127 | ---- 128 | 129 | To run a subset of tests:: 130 | 131 | $ py.test tests.test_zammad_py 132 | 133 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 3.0.0 (2023-02-27) 6 | ------------------ 7 | * release/3.0.0 8 | Add Pagination to search. This is an incompatible change to search API in olders versions. 9 | 10 | 2.0.0 (2023-02-14) 11 | ------------------ 12 | 13 | * release/2.0.0 14 | Documentation rewrite with more examples for use 15 | Rewrote Contributing Guide 16 | Added new Resource: Link 17 | Fixed Tests, Create and delete functions to not cause errors by calling the urls with expand=true added. 18 | 19 | 20 | 1.1.0 (2022-08-04) 21 | ------------------ 22 | 23 | * release/1.1.0 24 | Small Bugfixes, Linting 25 | Added TicketArticlePlain 26 | 27 | 28 | 1.0.1 (2021-05-28) 29 | ------------------ 30 | 31 | * release/1.0.1 32 | feat: Take url instead of host 33 | Update pytest, requests 34 | Fix Documentation 35 | 36 | 0.1.7 (2019-04-25) 37 | ------------------ 38 | 39 | * release/0.1.7 40 | feat: Make username, pass optional 41 | 42 | 43 | 0.1.6 (2019-04-11) 44 | ------------------ 45 | 46 | * release/0.1.6 47 | Update vcrpy, requests 48 | Fix Pagination, Add per_page getter / setter 49 | fix: Add context manager for doing req with behalf header 50 | 51 | 0.1.5 (2018-05-11) 52 | ------------------ 53 | 54 | * release/0.1.5 55 | Add more python versions to the test matrix 56 | 57 | 0.1.4 (2018-01-02) 58 | ------------------ 59 | 60 | * release/0.1.4: 61 | Released version 0.1.4 62 | Support indexing on paginated response 63 | Update pytest from 3.2.5 to 3.3.0 64 | Close session at exit 65 | Update pytest from 3.2.4 to 3.2.5 66 | Update pytest from 3.2.3 to 3.2.4 67 | API improvements 68 | Update README.rst 69 | Add pagination tests 70 | 71 | 0.1.3 (2017-11-13) 72 | ------------------ 73 | 74 | * release/0.1.3: 75 | Released version 0.1.3 76 | Add pagination support 77 | Add more resource endpoints 78 | Pin vcrpy to latest version 1.11.1 79 | Pin pytest to latest version 3.2.3 80 | Pin flake8 to latest version 3.5.0 81 | Pin requests to latest version 2.18.4 82 | 83 | 84 | 0.1.2 (2017-10-26) 85 | ------------------ 86 | 87 | * release/0.1.2: 88 | Released version 0.1.2 89 | Add toggle flag to use SSL or not 90 | Add tests for group resource 91 | Update README.md 92 | Add tests for ticket resource 93 | Fix json decode error 94 | master 95 | 96 | 0.1.0 (2017-10-25) 97 | ------------------ 98 | 99 | * Released version 0.1.1 100 | Test for all CRUD ops 101 | Add update method on base resource 102 | Add tests 103 | Update method in API 104 | Flake8ify 105 | Add basic zammad api 106 | Initial Boilerplate 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Joe Paul 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | 14 | define PRINT_HELP_PYSCRIPT 15 | import re, sys 16 | 17 | for line in sys.stdin: 18 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 19 | if match: 20 | target, help = match.groups() 21 | print("%-20s %s" % (target, help)) 22 | endef 23 | export PRINT_HELP_PYSCRIPT 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | 32 | clean-build: ## remove build artifacts 33 | rm -fr build/ 34 | rm -fr dist/ 35 | rm -fr .eggs/ 36 | find . -name '*.egg-info' -exec rm -fr {} + 37 | find . -name '*.egg' -exec rm -f {} + 38 | 39 | clean-pyc: ## remove Python file artifacts 40 | find . -name '*.pyc' -exec rm -f {} + 41 | find . -name '*.pyo' -exec rm -f {} + 42 | find . -name '*~' -exec rm -f {} + 43 | find . -name '__pycache__' -exec rm -fr {} + 44 | 45 | clean-test: ## remove test and coverage artifacts 46 | rm -fr .tox/ 47 | rm -f .coverage 48 | rm -fr htmlcov/ 49 | 50 | lint: ## check style with flake8 51 | flake8 zammad_py tests 52 | 53 | test: ## run tests quickly with the default Python 54 | py.test 55 | 56 | 57 | test-all: ## run tests on every Python version with tox 58 | tox 59 | 60 | coverage: ## check code coverage quickly with the default Python 61 | coverage run --source zammad_py -m pytest 62 | coverage report -m 63 | coverage html 64 | $(BROWSER) htmlcov/index.html 65 | 66 | docs: ## generate Sphinx HTML documentation, including API docs 67 | rm -f docs/zammad_py.rst 68 | rm -f docs/modules.rst 69 | sphinx-apidoc -o docs/ zammad_py 70 | $(MAKE) -C docs clean 71 | $(MAKE) -C docs html 72 | $(BROWSER) docs/_build/html/index.html 73 | 74 | servedocs: docs ## compile the docs watching for changes 75 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 76 | 77 | release: clean ## package and upload a release 78 | python setup.py sdist upload 79 | python setup.py bdist_wheel upload 80 | 81 | dist: clean ## builds source and wheel package 82 | python setup.py sdist 83 | python setup.py bdist_wheel 84 | ls -l dist 85 | 86 | install: clean ## install the package to the active Python's site-packages 87 | python setup.py install 88 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Zammad API Client 3 | ================= 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/zammad_py.svg 7 | :target: https://pypi.python.org/pypi/zammad_py 8 | 9 | .. image:: https://img.shields.io/travis/joeirimpan/zammad_py.svg 10 | :target: https://travis-ci.org/joeirimpan/zammad_py 11 | 12 | .. image:: https://readthedocs.org/projects/zammad-py/badge/?version=latest 13 | :target: https://zammad-py.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | .. image:: https://pyup.io/repos/github/joeirimpan/zammad_py/shield.svg 17 | :target: https://pyup.io/repos/github/joeirimpan/zammad_py/ 18 | :alt: Updates 19 | 20 | 21 | Python API client for zammad 22 | 23 | * Free software: MIT license 24 | * Documentation: https://zammad-py.readthedocs.io. 25 | 26 | 27 | Quickstart 28 | ---------- 29 | 30 | 31 | .. code-block:: python 32 | 33 | from zammad_py import ZammadAPI 34 | 35 | # Initialize the client with the URL, username, and password 36 | # Note the Host URL should be in this format: 'https://zammad.example.org/api/v1/' 37 | client = ZammadAPI(url='', username='', password='') 38 | 39 | # Example: Access all users 40 | this_page = client.user.all() 41 | for user in this_page: 42 | print(user) 43 | 44 | # Example: Get information about the current user 45 | print(client.user.me()) 46 | 47 | # Example: Create a ticket 48 | params = { 49 | "title": "Help me!", 50 | "group": "2nd Level", 51 | "customer": "david@example.com", 52 | "article": { 53 | "subject": "My subject", 54 | "body": "I am a message!", 55 | "type": "note", 56 | "internal": false 57 | } 58 | } 59 | new_ticket = client.ticket.create(params=params) 60 | 61 | 62 | 63 | General Methods 64 | --------------- 65 | Most resources support these methods: 66 | 67 | `.all()`: Returns a paginated response with the current page number and a list of elements. 68 | 69 | `.next_page()`: Returns the next page of the current pagination object. 70 | 71 | `.prev_page()`: Returns the previous page of the current pagination object. 72 | 73 | `.search(params)`: Returns a paginated response based on the search parameters. 74 | 75 | `.find(id)`: Returns a single object with the specified ID. 76 | 77 | `.create(params)`: Creates a new object with the specified parameters. 78 | 79 | `.update(params)`: Updates an existing object with the specified parameters. 80 | 81 | `.destroy(id)`: Deletes an object with the specified ID. 82 | 83 | Additional Resource Methods 84 | --------------------------- 85 | User resource also has the .me() method to get information about the current user. 86 | 87 | Ticket resource also has the .articles() method to get the articles associated with a ticket. 88 | 89 | Link resource has methods to list, add, and delete links between objects. 90 | 91 | TicketArticleAttachment resource has the .download() method to download a ticket attachment. 92 | 93 | Object resource has the .execute_migrations() method to run migrations on an object. 94 | 95 | You can set the `on_behalf_of` attribute of the ZammadAPI instance to do actions on behalf of another user. 96 | 97 | Contributing 98 | ------------ 99 | The Zammad API Client (zammad_py) welcomes contributions. 100 | 101 | You can contribute by reporting bugs, fixing bugs, implementing new features, writing documentation, and submitting feedback. 102 | 103 | To get started, see the contributing section in the docs! 104 | 105 | Please ensure that your changes include tests and updated documentation if necessary. 106 | 107 | Credits 108 | ------- 109 | 110 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 111 | 112 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 113 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 114 | 115 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /zammad_py.rst 2 | /zammad_py.*.rst 3 | /modules.rst 4 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/zammad_py.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zammad_py.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/zammad_py" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/zammad_py" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # zammad_py documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | 18 | # If extensions (or modules to document with autodoc) are in another 19 | # directory, add these directories to sys.path here. If the directory is 20 | # relative to the documentation root, use os.path.abspath to make it 21 | # absolute, like shown here. 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # Get the project root dir, which is the parent dir of this 25 | cwd = os.getcwd() 26 | project_root = os.path.dirname(cwd) 27 | 28 | # Insert the project root dir as the first element in the PYTHONPATH. 29 | # This lets us ensure that the source package is imported, and that its 30 | # version is used. 31 | sys.path.insert(0, project_root) 32 | 33 | import zammad_py # noqa: E402 34 | 35 | # -- General configuration --------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 42 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ["_templates"] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = ".rst" 49 | 50 | # The encoding of source files. 51 | # source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = "index" 55 | 56 | # General information about the project. 57 | project = "Zammad API Client" 58 | copyright = "2023, Joe Paul" 59 | 60 | # The version info for the project you're documenting, acts as replacement 61 | # for |version| and |release|, also used in various other places throughout 62 | # the built documents. 63 | # 64 | # The short X.Y version. 65 | version = zammad_py.__version__ 66 | # The full version, including alpha/beta/rc tags. 67 | release = zammad_py.__version__ 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to 74 | # some non-false value, then it is used: 75 | # today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | # today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ["_build"] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | # default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | # add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | # add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | # show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = "sphinx" 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | # modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built 105 | # documents. 106 | # keep_warnings = False 107 | 108 | 109 | # -- Options for HTML output ------------------------------------------- 110 | 111 | # The theme to use for HTML and HTML Help pages. See the documentation for 112 | # a list of builtin themes. 113 | html_theme = "default" 114 | 115 | # Theme options are theme-specific and customize the look and feel of a 116 | # theme further. For a list of options available for each theme, see the 117 | # documentation. 118 | # html_theme_options = {} 119 | 120 | # Add any paths that contain custom themes here, relative to this directory. 121 | # html_theme_path = [] 122 | 123 | # The name for this set of Sphinx documents. If None, it defaults to 124 | # " v documentation". 125 | # html_title = None 126 | 127 | # A shorter title for the navigation bar. Default is the same as 128 | # html_title. 129 | # html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the 132 | # top of the sidebar. 133 | # html_logo = None 134 | 135 | # The name of an image file (within the static path) to use as favicon 136 | # of the docs. This file should be a Windows icon file (.ico) being 137 | # 16x16 or 32x32 pixels large. 138 | # html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) 141 | # here, relative to this directory. They are copied after the builtin 142 | # static files, so a file named "default.css" will overwrite the builtin 143 | # "default.css". 144 | html_static_path = ["_static"] 145 | 146 | # If not '', a 'Last updated on:' timestamp is inserted at every page 147 | # bottom, using the given strftime format. 148 | # html_last_updated_fmt = '%b %d, %Y' 149 | 150 | # If true, SmartyPants will be used to convert quotes and dashes to 151 | # typographically correct entities. 152 | # html_use_smartypants = True 153 | 154 | # Custom sidebar templates, maps document names to template names. 155 | # html_sidebars = {} 156 | 157 | # Additional templates that should be rendered to pages, maps page names 158 | # to template names. 159 | # html_additional_pages = {} 160 | 161 | # If false, no module index is generated. 162 | # html_domain_indices = True 163 | 164 | # If false, no index is generated. 165 | # html_use_index = True 166 | 167 | # If true, the index is split into individual pages for each letter. 168 | # html_split_index = False 169 | 170 | # If true, links to the reST sources are added to the pages. 171 | # html_show_sourcelink = True 172 | 173 | # If true, "Created using Sphinx" is shown in the HTML footer. 174 | # Default is True. 175 | # html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. 178 | # Default is True. 179 | # html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages 182 | # will contain a tag referring to it. The value of this option 183 | # must be the base URL from which the finished HTML is served. 184 | # html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | # html_file_suffix = None 188 | 189 | # Output file base name for HTML help builder. 190 | htmlhelp_basename = "zammad_pydoc" 191 | 192 | 193 | # -- Options for LaTeX output ------------------------------------------ 194 | 195 | latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | # 'papersize': 'letterpaper', 198 | # The font size ('10pt', '11pt' or '12pt'). 199 | # 'pointsize': '10pt', 200 | # Additional stuff for the LaTeX preamble. 201 | # 'preamble': '', 202 | } 203 | 204 | # Grouping the document tree into LaTeX files. List of tuples 205 | # (source start file, target name, title, author, documentclass 206 | # [howto/manual]). 207 | latex_documents = [ 208 | ( 209 | "index", 210 | "zammad_py.tex", 211 | "Zammad API Client Documentation", 212 | "Joe Paul", 213 | "manual", 214 | ), 215 | ] 216 | 217 | # The name of an image file (relative to this directory) to place at 218 | # the top of the title page. 219 | # latex_logo = None 220 | 221 | # For "manual" documents, if this is true, then toplevel headings 222 | # are parts, not chapters. 223 | # latex_use_parts = False 224 | 225 | # If true, show page references after internal links. 226 | # latex_show_pagerefs = False 227 | 228 | # If true, show URL addresses after external links. 229 | # latex_show_urls = False 230 | 231 | # Documents to append as an appendix to all manuals. 232 | # latex_appendices = [] 233 | 234 | # If false, no module index is generated. 235 | # latex_domain_indices = True 236 | 237 | 238 | # -- Options for manual page output ------------------------------------ 239 | 240 | # One entry per manual page. List of tuples 241 | # (source start file, name, description, authors, manual section). 242 | man_pages = [("index", "zammad_py", "Zammad API Client Documentation", ["Joe Paul"], 1)] 243 | 244 | # If true, show URL addresses after external links. 245 | # man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ---------------------------------------- 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | texinfo_documents = [ 254 | ( 255 | "index", 256 | "zammad_py", 257 | "Zammad API Client Documentation", 258 | "Joe Paul", 259 | "zammad_py", 260 | "Python Module for interacting with the zammad api.", 261 | "Miscellaneous", 262 | ), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | # texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | # texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | # texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | # texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Zammad API Client's documentation! 2 | ============================================= 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | readme 10 | installation 11 | usage 12 | modules 13 | contributing 14 | authors 15 | history 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install Zammad API Client, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install zammad_py 16 | 17 | This is the preferred method to install Zammad API Client, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for Zammad API Client can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/joeirimpan/zammad_py 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/joeirimpan/zammad_py/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/joeirimpan/zammad_py 51 | .. _tarball: https://github.com/joeirimpan/zammad_py/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\zammad_py.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\zammad_py.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use Zammad API Client in a project:: 6 | 7 | from zammad_py import ZammadAPI 8 | # Note the Host URL should be in this format: 'https://zammad.example.org/api/v1/' 9 | client = ZammadAPI(url='', username='', password='') 10 | 11 | Zammad Resources are implemented as an abstract class (Resource), meaning most objects have the same mechanisms. 12 | Because Zammad uses pagination (https://docs.zammad.org/en/latest/api/intro.html#pagination), the pagination is very helpful. 13 | 14 | General Methods 15 | --------------- 16 | 17 | Most Resources support these methods. 18 | 19 | .all() 20 | | Returns a paginated response with _page containing the page number and _items containing a list of elements. 21 | | This allows to iterate over the objects. 22 | 23 | .. code-block:: python 24 | 25 | from zammad_py import ZammadAPI 26 | client = ZammadAPI(url='', username='', password='') 27 | # all return a paginated response 28 | this_page = client.user.all() 29 | # Iterate through page object 30 | for user in this_page: 31 | print(user) 32 | 33 | 34 | .next_page() 35 | | Can be called from the current page (returned by .all()) 36 | | Contains the next page object if there are any. 37 | 38 | .. code-block:: python 39 | 40 | next_page = this_page.next_page() 41 | 42 | 43 | .prev_page() 44 | | Can be called from the current page (pagination object) 45 | | Contains the previous page object if there are any. 46 | 47 | .. code-block:: python 48 | 49 | prev_page = this_page.prev_page() 50 | 51 | 52 | .search(search_string) 53 | | Searches the object with a Zammad search query 54 | | Learn more about Zammad Search queries here: https://user-docs.zammad.org/en/latest/advanced/search.html 55 | 56 | .. code-block:: python 57 | 58 | from zammad_py import ZammadAPI 59 | client = ZammadAPI(url='', username='', password='') 60 | client.ticket.search('Search Content') 61 | 62 | 63 | .find(id) 64 | | Displays a Resource if you know the id. (Returns a dict) 65 | 66 | .. code-block:: python 67 | 68 | from zammad_py import ZammadAPI 69 | client = ZammadAPI(url='', username='', password='') 70 | client.ticket.find() 71 | 72 | 73 | .create(params) 74 | | Creates a new Resource. 75 | | You can find the required structure for the params in the Zammad API Documentation. 76 | 77 | .. code-block:: python 78 | 79 | from zammad_py import ZammadAPI 80 | client = ZammadAPI(url='', username='', password='') 81 | params = { 82 | "title": "Help me!", 83 | "group": "2nd Level", 84 | "customer": "david@example.com", 85 | "article": { 86 | "subject": "My subject", 87 | "body": "I am a message!", 88 | "type": "note", 89 | "internal": false 90 | } 91 | } 92 | new_ticket = client.ticket.create(params=params) 93 | 94 | 95 | .update(params) 96 | | Updates a resource. 97 | | You can find the required structure for the params in the Zammad API Documentation, or use a dict provided by the .find(id) functionaliy. 98 | 99 | .. code-block:: python 100 | 101 | from zammad_py import ZammadAPI 102 | client = ZammadAPI(url='', username='', password='') 103 | org = client.organization.find() 104 | org['name'] = 'NewCompanyName Ltd.' 105 | client.organization.update(id=org['id'],params=org) 106 | 107 | .destroy(id) 108 | | Deletes a Resource. 109 | | For some resources, you need special privileges. Refer to the Zammad API Documentation. 110 | 111 | .. code-block:: python 112 | 113 | from zammad_py import ZammadAPI 114 | client = ZammadAPI(url='', username='', password='') 115 | client.organization.destroy() 116 | 117 | 118 | Available Resources 119 | user 120 | organization 121 | group 122 | ticket 123 | link 124 | ticketarticle 125 | ticketarticleplain 126 | ticketpriority 127 | ticketstate 128 | tickettag 129 | object 130 | taglist 131 | 132 | User Resource 133 | ------------- 134 | 135 | The :class:`~zammad_py.api.User` resource also has the :meth:`~zammad_py.api.User.me()` method to get information about the current user. 136 | 137 | .. code-block:: python 138 | 139 | from zammad_py import ZammadAPI 140 | client = ZammadAPI(url='', username='', password='') 141 | print(client.user.me()) 142 | 143 | 144 | Ticket Resource 145 | --------------- 146 | 147 | The :class:`~zammad_py.api.Ticket` resource also has the :meth:`~zammad_py.api.Ticket.articles()` method to get the articles associated to the ticket. 148 | 149 | .. code-block:: python 150 | 151 | from zammad_py import ZammadAPI 152 | client = ZammadAPI(url='', username='', password='') 153 | print(client.ticket.find()) 154 | ticketarticles = client.ticket.articles 155 | print(ticketarticles) 156 | 157 | The :class:`~zammad_py.api.Ticket` resource also has the :meth:`~zammad_py.api.Ticket.tags()` method to get the tags associated to the ticket. 158 | 159 | .. code-block:: python 160 | 161 | from zammad_py import ZammadAPI 162 | client = ZammadAPI(url='', username='', password='') 163 | print(client.ticket.find()) 164 | ticket_tags = client.ticket.tags(()) 165 | for ttag in ticket_tags['tags']: 166 | print(ttga) 167 | 168 | Further, it has the :meth:`~zammad_py.api.Ticket.merge()` method, that allows to merge two tickets. (This is not documented in the Zammad API Documentation) 169 | The method requires the Ticket id of the Child (The ticket you want to merge into the parent) and the Ticket Number of the Parent Ticket. (The ticket you want to contain the articles of the child after merging.) 170 | 171 | Important: If you want to use the merge functionality, you need to use password, not http_token for your authentication. 172 | 173 | .. code-block:: python 174 | 175 | from zammad_py import ZammadAPI 176 | client = ZammadAPI(url='', username='', password='') 177 | client.ticket.merge(id=,number=) 178 | 179 | 180 | Link Resource 181 | ------------- 182 | 183 | The :class:`~zammad_py.api.Link` resource also has methods to list, add and delete Links between objects. 184 | 185 | :meth:`zammad_py.api.Link.get` 186 | | This returns all links associated with the ticket ID provided 187 | 188 | :meth:`zammad_py.api.Link.add` 189 | | Create a Link between two objects. (Currently, and by default Tickets) 190 | 191 | :meth:`zammad_py.api.Link.remove` 192 | | Remove a Link between two objects. (Currently, and by default Tickets) 193 | 194 | .. code-block:: python 195 | 196 | from zammad_py import ZammadAPI 197 | client = ZammadAPI(url='', username='', password='') 198 | print(client.link.get()) 199 | ticketarticles = client.ticket.articles 200 | print(ticketarticles) 201 | 202 | TicketArticleAttachment Resource 203 | -------------------------------- 204 | 205 | The :class:`~zammad_py.api.TicketArticleAttachment` resource has the :meth:`~zammad_py.api.TicketArticleAttachment.download()` method. 206 | 207 | .. code-block:: python 208 | 209 | """Download the ticket attachment associated with the ticket id 210 | 211 | :param id: Ticket attachment id 212 | :param article_id: Ticket article id 213 | :param ticket_id: Ticket id 214 | """ 215 | 216 | TagList Resource 217 | ---------------- 218 | 219 | The :class `~zammad_py.api.TagList` resource handles tags in the admin scope and has methods to create, remove and list tags. 220 | 221 | :meth:`zammad_py.api.TagList.all` 222 | | This returns all tags configured (paginated). 223 | 224 | :meth:`zammad_py.api.TagList.create` 225 | | Creates a new tag. 226 | 227 | :meth:`zammad_py.api.TagList.destroy` 228 | | Deletes a tag with the ID provided. 229 | 230 | .. code-block:: python 231 | 232 | from zammad_py import ZammadAPI 233 | client = ZammadAPI(url='', username='', password='') 234 | client.taglist.create({'name': 'TestTag'}) 235 | for tag in client.taglist.all(): 236 | print(tag) 237 | if tag['name'] == 'TestTag': 238 | client.taglist.destroy(tag['id']) 239 | 240 | TicketTag Resource 241 | ------------------- 242 | 243 | The :class `~zammad_py.api.TicketTag` resource handles tags in the ticket scope and has methods to add and remove tags associated to tickets. 244 | 245 | :meth:`zammad_py.api.TicketTag.add` 246 | | Add a new tag to a ticket. (This will create the tag if it doesn’t exist and the user has permission to do so.) 247 | 248 | 249 | :meth:`zammad_py.api.TicketTag.remove` 250 | | Remove a tag from a ticket. 251 | 252 | .. code-block:: python 253 | 254 | from zammad_py import ZammadAPI 255 | client = ZammadAPI(url='', username='', password='') 256 | ticket_tags = client.ticket.tags(()) 257 | print(ticket_tags['tags']) # ['TestTag', 'Zammad'] 258 | client.ticket_tag.add(, 'FancyNewTag') 259 | client.ticket_tag.remove(, 'Zammad') 260 | ticket_tags = client.ticket.tags(()) 261 | print(ticket_tags['tags']) # ['TestTag', 'FancyNewTag'] 262 | 263 | Object Resource 264 | --------------- 265 | The :class:`~zammad_py.api.Object` resource has the :meth:`~zammad_py.api.Object.execute_migrations()` method to run the migrations of an object. 266 | 267 | Using "On behalf of" 268 | -------------------- 269 | 270 | To do actions on behalf of another user, just set the `on_behalf_of` attribute on the instance of ZammadAPI 271 | 272 | 273 | .. code-block:: python 274 | 275 | from zammad_py import ZammadAPI 276 | client = ZammadAPI(url='', username='', password='') 277 | client.on_behalf_of = 'test@user.com' 278 | # Do stuff... 279 | 280 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "zammad_py" 3 | version = "3.2.0" 4 | readme = "README.rst" 5 | description = "Python API client for zammad" 6 | authors = ["Joe Paul "] 7 | license = "MIT" 8 | classifiers = [ 9 | 'Development Status :: 4 - Beta', 10 | 'Intended Audience :: Developers', 11 | 'License :: OSI Approved :: MIT License', 12 | 'Natural Language :: English', 13 | 'Programming Language :: Python :: 3.9', 14 | 'Programming Language :: Python :: 3.10', 15 | ] 16 | homepage = "https://github.com/joeirimpan/zammad_py" 17 | documentation = "https://zammad-py.readthedocs.io/" 18 | repository = "https://github.com/joeirimpan/zammad_py.git" 19 | 20 | [tool.poetry.dependencies] 21 | python = ">=3.9,<4.0" 22 | requests = "^2.25.1" 23 | 24 | [tool.poetry.group.dev.dependencies] 25 | pytest = "^8.0.0" 26 | vcrpy = "^5.1.0" 27 | flake8 = "7.2.0" 28 | black = "25.1.0" 29 | isort = "6.0.1" 30 | pytest-vcr = "^1.0.2" 31 | 32 | [tool.mypy] 33 | files = "zammad_py" 34 | 35 | [build-system] 36 | requires = ["poetry-core>=1.0.0"] 37 | build-backend = "poetry.core.masonry.api" 38 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.5 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:zammad_py/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | max-line-length = 150 20 | extend-ignore = E203 21 | 22 | [aliases] 23 | test = pytest 24 | # Define setup.py command aliases here 25 | -------------------------------------------------------------------------------- /tests/cassettes/TestAPI.test_groups.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Authorization: 10 | - user 11 | Connection: 12 | - keep-alive 13 | User-Agent: 14 | - Zammad API Python 15 | method: GET 16 | uri: http://localhost:8080/api/v1/groups?page=1&per_page=10&expand=true 17 | response: 18 | body: 19 | string: !!binary | 20 | H4sIAAAAAAAEA9WSy07DMBBFf6XyOg15NGrrFTt2CImyoYosN57QgGNHfgAR6r9jJy2J0hbWLH11 21 | 5854zmy/UMUQjgOkqxdBjVVAjgLUtOKEMqZA604UlvMACVoDwuhJg9KofxJOtRlpDVUgzKiGah9f 22 | e9FUNUjr3H1aKTmXH8Q2pJHOtOM+uwWfrEA2ILoCUgnCaKvPq4ZkhI2yECBamOrdpfQvvXezMMIU 23 | LY0r70UhjW/zaKhgVLHZnZK2uXmQks9KqWabqngDo0M3g20YNa5+157WUii4olC/giRKsnmUzeN0 24 | E61wusRxFmbJ4nmUdcG3SHGUhMtl73Obdd3ctNs0D9DQ0MXPRzm79ij8oHNvBiW13HhbBwhv0avc 25 | CybhFj5p3XAIC1mj/BD05NMp+R7LH/Dv3Q3Eroe/hRP8k/ZP4Psb8F+YEHb7GBbeMR8pZ+TWOFrj 26 | eBGm2epXwlPfQHgK+BKs8ZAO8RXLEXd+yL8BUF6mldUDAAA= 27 | headers: 28 | Access-Control-Allow-Headers: 29 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 30 | X-File-Name, Cache-Control, Accept-Language 31 | Access-Control-Allow-Methods: 32 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 33 | Access-Control-Allow-Origin: 34 | - '*' 35 | Access-Control-Max-Age: 36 | - '1728000' 37 | Cache-Control: 38 | - max-age=0, private, must-revalidate 39 | Connection: 40 | - keep-alive 41 | Content-Encoding: 42 | - gzip 43 | Content-Security-Policy: 44 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 45 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 46 | ''self'' ''unsafe-eval'' ''nonce-yZPFSTnIMp0rdm+1VI7huQ==''; style-src ''self'' 47 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 48 | Content-Type: 49 | - application/json; charset=utf-8 50 | Date: 51 | - Tue, 13 May 2025 09:11:09 GMT 52 | ETag: 53 | - W/"eb559e8183d9849d5332f34b0a211bbd" 54 | Referrer-Policy: 55 | - strict-origin-when-cross-origin 56 | Server: 57 | - nginx 58 | Transfer-Encoding: 59 | - chunked 60 | Vary: 61 | - Accept 62 | X-Content-Type-Options: 63 | - nosniff 64 | X-Download-Options: 65 | - noopen 66 | X-Frame-Options: 67 | - SAMEORIGIN 68 | X-Permitted-Cross-Domain-Policies: 69 | - none 70 | X-Request-Id: 71 | - b859322d-cdca-4bc4-9ffd-32f6929688f6 72 | X-Runtime: 73 | - '0.265944' 74 | X-XSS-Protection: 75 | - 1; mode=block 76 | status: 77 | code: 200 78 | message: OK 79 | - request: 80 | body: null 81 | headers: 82 | Accept: 83 | - '*/*' 84 | Accept-Encoding: 85 | - gzip, deflate 86 | Authorization: 87 | - user 88 | Connection: 89 | - keep-alive 90 | User-Agent: 91 | - Zammad API Python 92 | method: GET 93 | uri: http://localhost:8080/api/v1/groups/1 94 | response: 95 | body: 96 | string: !!binary | 97 | H4sIAAAAAAAEA3WQ3UrEMBCFX6Xkulv7s6WSF/BWcPdmRcJsM3WDaRImiVLEdzfZKhbUyzmc883h 98 | vDMlGW9K5tWzgRAJxZeAMygtQEpC76+iiVqXzMCMjLOjR/JsPYUGHzaaA0ITNhnwGT9nMagZbUzu 99 | lTZZre2biE44m0xnndkLZjKhdWiuAaGMkLD436kfMuOBIpYMxqBeE2W9/CV1kUISTCHFV9HYkN88 100 | BDASSBZ3ZKO7ubdWF5Ol4qDGFwy+Sh2ikxBS/rx8zzIS/qNAnqCt235X97umO9S3vBt401d9uz9t 101 | WH/49h2v22oYVl9aNn1LbR+7p49PMby2RaEBAAA= 102 | headers: 103 | Access-Control-Allow-Headers: 104 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 105 | X-File-Name, Cache-Control, Accept-Language 106 | Access-Control-Allow-Methods: 107 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 108 | Access-Control-Allow-Origin: 109 | - '*' 110 | Access-Control-Max-Age: 111 | - '1728000' 112 | Cache-Control: 113 | - max-age=0, private, must-revalidate 114 | Connection: 115 | - keep-alive 116 | Content-Encoding: 117 | - gzip 118 | Content-Security-Policy: 119 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 120 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 121 | ''self'' ''unsafe-eval'' ''nonce-0u7Ajte6hT6y2gzDybuQsw==''; style-src ''self'' 122 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 123 | Content-Type: 124 | - application/json; charset=utf-8 125 | Date: 126 | - Tue, 13 May 2025 09:11:09 GMT 127 | ETag: 128 | - W/"a4e328af1283ce48c215c2e10a27381e" 129 | Referrer-Policy: 130 | - strict-origin-when-cross-origin 131 | Server: 132 | - nginx 133 | Transfer-Encoding: 134 | - chunked 135 | Vary: 136 | - Accept 137 | X-Content-Type-Options: 138 | - nosniff 139 | X-Download-Options: 140 | - noopen 141 | X-Frame-Options: 142 | - SAMEORIGIN 143 | X-Permitted-Cross-Domain-Policies: 144 | - none 145 | X-Request-Id: 146 | - e2259e28-25fb-47a0-9120-516a9ef9863b 147 | X-Runtime: 148 | - '0.246636' 149 | X-XSS-Protection: 150 | - 1; mode=block 151 | status: 152 | code: 200 153 | message: OK 154 | - request: 155 | body: '{"name": "Test1", "note": "note1"}' 156 | headers: 157 | Accept: 158 | - '*/*' 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Authorization: 162 | - user 163 | Connection: 164 | - keep-alive 165 | Content-Length: 166 | - '34' 167 | Content-Type: 168 | - application/json 169 | User-Agent: 170 | - Zammad API Python 171 | method: POST 172 | uri: http://localhost:8080/api/v1/groups 173 | response: 174 | body: 175 | string: '{"id":5,"signature_id":null,"email_address_id":null,"name":"Test1","name_last":"Test1","parent_id":null,"assignment_timeout":null,"follow_up_possible":"yes","reopen_time_in_days":null,"follow_up_assignment":true,"active":true,"shared_drafts":true,"note":"note1","updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:09.897Z","updated_at":"2025-05-13T09:11:09.897Z","user_ids":[]}' 176 | headers: 177 | Access-Control-Allow-Headers: 178 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 179 | X-File-Name, Cache-Control, Accept-Language 180 | Access-Control-Allow-Methods: 181 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 182 | Access-Control-Allow-Origin: 183 | - '*' 184 | Access-Control-Max-Age: 185 | - '1728000' 186 | Cache-Control: 187 | - max-age=0, private, must-revalidate 188 | Connection: 189 | - keep-alive 190 | Content-Length: 191 | - '392' 192 | Content-Security-Policy: 193 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 194 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 195 | ''self'' ''unsafe-eval'' ''nonce-wPt+xgm7G7GoYwLhM8PKig==''; style-src ''self'' 196 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 197 | Content-Type: 198 | - application/json; charset=utf-8 199 | Date: 200 | - Tue, 13 May 2025 09:11:10 GMT 201 | ETag: 202 | - W/"237f724cf87f53d1dcafe3ce0ad109d0" 203 | Referrer-Policy: 204 | - strict-origin-when-cross-origin 205 | Server: 206 | - nginx 207 | Vary: 208 | - Accept 209 | X-Content-Type-Options: 210 | - nosniff 211 | X-Download-Options: 212 | - noopen 213 | X-Frame-Options: 214 | - SAMEORIGIN 215 | X-Permitted-Cross-Domain-Policies: 216 | - none 217 | X-Request-Id: 218 | - b9d37a37-abc9-4c4f-a051-53798cab0208 219 | X-Runtime: 220 | - '0.392146' 221 | X-XSS-Protection: 222 | - 1; mode=block 223 | status: 224 | code: 201 225 | message: Created 226 | - request: 227 | body: '{"name": "Test2"}' 228 | headers: 229 | Accept: 230 | - '*/*' 231 | Accept-Encoding: 232 | - gzip, deflate 233 | Authorization: 234 | - user 235 | Connection: 236 | - keep-alive 237 | Content-Length: 238 | - '17' 239 | Content-Type: 240 | - application/json 241 | User-Agent: 242 | - Zammad API Python 243 | method: PUT 244 | uri: http://localhost:8080/api/v1/groups/5 245 | response: 246 | body: 247 | string: !!binary | 248 | H4sIAAAAAAAEA21Q207EIBD9l3lmN9BNo+U79kljyOwyqyQUCAyaxvjvQquxUZ8IZ84t5x0CzgQa 249 | zlR4AAE1WWSy5rIYZ0GfxEowHgvvWP00CijuOSDXTCs3VO8F0IzOG7Q2Uyk7PGGmwDsAS5fPHWQ3 250 | U6wtYLO4Re/jm6nJpNhIF98LLlRavUwxUVgFxgVjcSl/VT/OoDlXEoBXdq/NZfuVl9bFGpvxxk2+ 251 | gSFyj+mPakHXTL92+EawDzHIYTzI8aBOZzlppbScjvfT3cNuwf95Sh6HL16h3OZoBR6fPj4BdMl7 252 | VIgBAAA= 253 | headers: 254 | Access-Control-Allow-Headers: 255 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 256 | X-File-Name, Cache-Control, Accept-Language 257 | Access-Control-Allow-Methods: 258 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 259 | Access-Control-Allow-Origin: 260 | - '*' 261 | Access-Control-Max-Age: 262 | - '1728000' 263 | Cache-Control: 264 | - max-age=0, private, must-revalidate 265 | Connection: 266 | - keep-alive 267 | Content-Encoding: 268 | - gzip 269 | Content-Security-Policy: 270 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 271 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 272 | ''self'' ''unsafe-eval'' ''nonce-QEskPWqjIzqYzMyaZIVdPg==''; style-src ''self'' 273 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 274 | Content-Type: 275 | - application/json; charset=utf-8 276 | Date: 277 | - Tue, 13 May 2025 09:11:10 GMT 278 | ETag: 279 | - W/"f8ae22774e242b3650d45d0e9c0ba718" 280 | Referrer-Policy: 281 | - strict-origin-when-cross-origin 282 | Server: 283 | - nginx 284 | Transfer-Encoding: 285 | - chunked 286 | Vary: 287 | - Accept 288 | X-Content-Type-Options: 289 | - nosniff 290 | X-Download-Options: 291 | - noopen 292 | X-Frame-Options: 293 | - SAMEORIGIN 294 | X-Permitted-Cross-Domain-Policies: 295 | - none 296 | X-Request-Id: 297 | - af46420a-9794-4027-a333-420ee4bd06a0 298 | X-Runtime: 299 | - '0.376109' 300 | X-XSS-Protection: 301 | - 1; mode=block 302 | status: 303 | code: 200 304 | message: OK 305 | - request: 306 | body: null 307 | headers: 308 | Accept: 309 | - '*/*' 310 | Accept-Encoding: 311 | - gzip, deflate 312 | Authorization: 313 | - user 314 | Connection: 315 | - keep-alive 316 | Content-Length: 317 | - '0' 318 | User-Agent: 319 | - Zammad API Python 320 | method: DELETE 321 | uri: http://localhost:8080/api/v1/groups/5 322 | response: 323 | body: 324 | string: '{}' 325 | headers: 326 | Access-Control-Allow-Headers: 327 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 328 | X-File-Name, Cache-Control, Accept-Language 329 | Access-Control-Allow-Methods: 330 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 331 | Access-Control-Allow-Origin: 332 | - '*' 333 | Access-Control-Max-Age: 334 | - '1728000' 335 | Cache-Control: 336 | - max-age=0, private, must-revalidate 337 | Connection: 338 | - keep-alive 339 | Content-Length: 340 | - '2' 341 | Content-Security-Policy: 342 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 343 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 344 | ''self'' ''unsafe-eval'' ''nonce-I3IkuYVUqtLAkwzIGfk/IQ==''; style-src ''self'' 345 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 346 | Content-Type: 347 | - application/json; charset=utf-8 348 | Date: 349 | - Tue, 13 May 2025 09:11:10 GMT 350 | ETag: 351 | - W/"44136fa355b3678a1146ad16f7e8649e" 352 | Referrer-Policy: 353 | - strict-origin-when-cross-origin 354 | Server: 355 | - nginx 356 | Vary: 357 | - Accept 358 | X-Content-Type-Options: 359 | - nosniff 360 | X-Download-Options: 361 | - noopen 362 | X-Frame-Options: 363 | - SAMEORIGIN 364 | X-Permitted-Cross-Domain-Policies: 365 | - none 366 | X-Request-Id: 367 | - b3120b42-97a4-4e0e-af26-b9089712a68d 368 | X-Runtime: 369 | - '0.342268' 370 | X-XSS-Protection: 371 | - 1; mode=block 372 | status: 373 | code: 200 374 | message: OK 375 | version: 1 376 | -------------------------------------------------------------------------------- /tests/cassettes/TestAPI.test_pagination.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: '{"email": "pytest0@example.com"}' 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Authorization: 10 | - user 11 | Connection: 12 | - keep-alive 13 | Content-Length: 14 | - '32' 15 | Content-Type: 16 | - application/json 17 | User-Agent: 18 | - Zammad API Python 19 | method: POST 20 | uri: http://localhost:8080/api/v1/users 21 | response: 22 | body: 23 | string: '{"id":68,"organization_id":null,"login":"pytest0@example.com","firstname":"","lastname":"","email":"pytest0@example.com","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:11.067Z","updated_at":"2025-05-13T09:11:11.067Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 24 | headers: 25 | Access-Control-Allow-Headers: 26 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 27 | X-File-Name, Cache-Control, Accept-Language 28 | Access-Control-Allow-Methods: 29 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 30 | Access-Control-Allow-Origin: 31 | - '*' 32 | Access-Control-Max-Age: 33 | - '1728000' 34 | Cache-Control: 35 | - max-age=0, private, must-revalidate 36 | Connection: 37 | - keep-alive 38 | Content-Length: 39 | - '771' 40 | Content-Security-Policy: 41 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 42 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 43 | ''self'' ''unsafe-eval'' ''nonce-GOBbXeHslNB5r0K/uDTz0Q==''; style-src ''self'' 44 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 45 | Content-Type: 46 | - application/json; charset=utf-8 47 | Date: 48 | - Tue, 13 May 2025 09:11:11 GMT 49 | ETag: 50 | - W/"40b18d94ea389394e465e46ae50f6829" 51 | Referrer-Policy: 52 | - strict-origin-when-cross-origin 53 | Server: 54 | - nginx 55 | Vary: 56 | - Accept 57 | X-Content-Type-Options: 58 | - nosniff 59 | X-Download-Options: 60 | - noopen 61 | X-Frame-Options: 62 | - SAMEORIGIN 63 | X-Permitted-Cross-Domain-Policies: 64 | - none 65 | X-Request-Id: 66 | - cff57caa-a535-4acd-8c15-57b09d8473d3 67 | X-Runtime: 68 | - '0.439257' 69 | X-XSS-Protection: 70 | - 1; mode=block 71 | status: 72 | code: 201 73 | message: Created 74 | - request: 75 | body: '{"email": "pytest1@example.com"}' 76 | headers: 77 | Accept: 78 | - '*/*' 79 | Accept-Encoding: 80 | - gzip, deflate 81 | Authorization: 82 | - user 83 | Connection: 84 | - keep-alive 85 | Content-Length: 86 | - '32' 87 | Content-Type: 88 | - application/json 89 | User-Agent: 90 | - Zammad API Python 91 | method: POST 92 | uri: http://localhost:8080/api/v1/users 93 | response: 94 | body: 95 | string: '{"id":69,"organization_id":null,"login":"pytest1@example.com","firstname":"","lastname":"","email":"pytest1@example.com","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:11.530Z","updated_at":"2025-05-13T09:11:11.530Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 96 | headers: 97 | Access-Control-Allow-Headers: 98 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 99 | X-File-Name, Cache-Control, Accept-Language 100 | Access-Control-Allow-Methods: 101 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 102 | Access-Control-Allow-Origin: 103 | - '*' 104 | Access-Control-Max-Age: 105 | - '1728000' 106 | Cache-Control: 107 | - max-age=0, private, must-revalidate 108 | Connection: 109 | - keep-alive 110 | Content-Length: 111 | - '771' 112 | Content-Security-Policy: 113 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 114 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 115 | ''self'' ''unsafe-eval'' ''nonce-Vvoji926diR7Ps0xFMVt8A==''; style-src ''self'' 116 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 117 | Content-Type: 118 | - application/json; charset=utf-8 119 | Date: 120 | - Tue, 13 May 2025 09:11:11 GMT 121 | ETag: 122 | - W/"6b38094f989b28129820fe3c528fe8b8" 123 | Referrer-Policy: 124 | - strict-origin-when-cross-origin 125 | Server: 126 | - nginx 127 | Vary: 128 | - Accept 129 | X-Content-Type-Options: 130 | - nosniff 131 | X-Download-Options: 132 | - noopen 133 | X-Frame-Options: 134 | - SAMEORIGIN 135 | X-Permitted-Cross-Domain-Policies: 136 | - none 137 | X-Request-Id: 138 | - dff96fce-7df8-4954-8088-84f084e1036b 139 | X-Runtime: 140 | - '0.471300' 141 | X-XSS-Protection: 142 | - 1; mode=block 143 | status: 144 | code: 201 145 | message: Created 146 | - request: 147 | body: '{"email": "pytest2@example.com"}' 148 | headers: 149 | Accept: 150 | - '*/*' 151 | Accept-Encoding: 152 | - gzip, deflate 153 | Authorization: 154 | - user 155 | Connection: 156 | - keep-alive 157 | Content-Length: 158 | - '32' 159 | Content-Type: 160 | - application/json 161 | User-Agent: 162 | - Zammad API Python 163 | method: POST 164 | uri: http://localhost:8080/api/v1/users 165 | response: 166 | body: 167 | string: '{"id":70,"organization_id":null,"login":"pytest2@example.com","firstname":"","lastname":"","email":"pytest2@example.com","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:12.128Z","updated_at":"2025-05-13T09:11:12.128Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 168 | headers: 169 | Access-Control-Allow-Headers: 170 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 171 | X-File-Name, Cache-Control, Accept-Language 172 | Access-Control-Allow-Methods: 173 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 174 | Access-Control-Allow-Origin: 175 | - '*' 176 | Access-Control-Max-Age: 177 | - '1728000' 178 | Cache-Control: 179 | - max-age=0, private, must-revalidate 180 | Connection: 181 | - keep-alive 182 | Content-Length: 183 | - '771' 184 | Content-Security-Policy: 185 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 186 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 187 | ''self'' ''unsafe-eval'' ''nonce-MpkGRtur+W39IgZH2y+4Xw==''; style-src ''self'' 188 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 189 | Content-Type: 190 | - application/json; charset=utf-8 191 | Date: 192 | - Tue, 13 May 2025 09:11:12 GMT 193 | ETag: 194 | - W/"c4bccbf29098f01305dbff3741e4af11" 195 | Referrer-Policy: 196 | - strict-origin-when-cross-origin 197 | Server: 198 | - nginx 199 | Vary: 200 | - Accept 201 | X-Content-Type-Options: 202 | - nosniff 203 | X-Download-Options: 204 | - noopen 205 | X-Frame-Options: 206 | - SAMEORIGIN 207 | X-Permitted-Cross-Domain-Policies: 208 | - none 209 | X-Request-Id: 210 | - 2b7c1d4c-c5ae-41df-8eca-67c9933fd01b 211 | X-Runtime: 212 | - '0.571677' 213 | X-XSS-Protection: 214 | - 1; mode=block 215 | status: 216 | code: 201 217 | message: Created 218 | - request: 219 | body: '{"email": "pytest3@example.com"}' 220 | headers: 221 | Accept: 222 | - '*/*' 223 | Accept-Encoding: 224 | - gzip, deflate 225 | Authorization: 226 | - user 227 | Connection: 228 | - keep-alive 229 | Content-Length: 230 | - '32' 231 | Content-Type: 232 | - application/json 233 | User-Agent: 234 | - Zammad API Python 235 | method: POST 236 | uri: http://localhost:8080/api/v1/users 237 | response: 238 | body: 239 | string: '{"id":71,"organization_id":null,"login":"pytest3@example.com","firstname":"","lastname":"","email":"pytest3@example.com","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:12.586Z","updated_at":"2025-05-13T09:11:12.586Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 240 | headers: 241 | Access-Control-Allow-Headers: 242 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 243 | X-File-Name, Cache-Control, Accept-Language 244 | Access-Control-Allow-Methods: 245 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 246 | Access-Control-Allow-Origin: 247 | - '*' 248 | Access-Control-Max-Age: 249 | - '1728000' 250 | Cache-Control: 251 | - max-age=0, private, must-revalidate 252 | Connection: 253 | - keep-alive 254 | Content-Length: 255 | - '771' 256 | Content-Security-Policy: 257 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 258 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 259 | ''self'' ''unsafe-eval'' ''nonce-yJve/R6/vRVQmZyGyqSC0A==''; style-src ''self'' 260 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 261 | Content-Type: 262 | - application/json; charset=utf-8 263 | Date: 264 | - Tue, 13 May 2025 09:11:12 GMT 265 | ETag: 266 | - W/"077238ff670c6fea968a19279cd5c30d" 267 | Referrer-Policy: 268 | - strict-origin-when-cross-origin 269 | Server: 270 | - nginx 271 | Vary: 272 | - Accept 273 | X-Content-Type-Options: 274 | - nosniff 275 | X-Download-Options: 276 | - noopen 277 | X-Frame-Options: 278 | - SAMEORIGIN 279 | X-Permitted-Cross-Domain-Policies: 280 | - none 281 | X-Request-Id: 282 | - bb812f85-56ba-4c8b-ad09-95e317f9f1af 283 | X-Runtime: 284 | - '0.446883' 285 | X-XSS-Protection: 286 | - 1; mode=block 287 | status: 288 | code: 201 289 | message: Created 290 | - request: 291 | body: '{"email": "pytest4@example.com"}' 292 | headers: 293 | Accept: 294 | - '*/*' 295 | Accept-Encoding: 296 | - gzip, deflate 297 | Authorization: 298 | - user 299 | Connection: 300 | - keep-alive 301 | Content-Length: 302 | - '32' 303 | Content-Type: 304 | - application/json 305 | User-Agent: 306 | - Zammad API Python 307 | method: POST 308 | uri: http://localhost:8080/api/v1/users 309 | response: 310 | body: 311 | string: '{"id":72,"organization_id":null,"login":"pytest4@example.com","firstname":"","lastname":"","email":"pytest4@example.com","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:13.064Z","updated_at":"2025-05-13T09:11:13.064Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 312 | headers: 313 | Access-Control-Allow-Headers: 314 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 315 | X-File-Name, Cache-Control, Accept-Language 316 | Access-Control-Allow-Methods: 317 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 318 | Access-Control-Allow-Origin: 319 | - '*' 320 | Access-Control-Max-Age: 321 | - '1728000' 322 | Cache-Control: 323 | - max-age=0, private, must-revalidate 324 | Connection: 325 | - keep-alive 326 | Content-Length: 327 | - '771' 328 | Content-Security-Policy: 329 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 330 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 331 | ''self'' ''unsafe-eval'' ''nonce-r3aRjtZIUetRiS3seCkyXg==''; style-src ''self'' 332 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 333 | Content-Type: 334 | - application/json; charset=utf-8 335 | Date: 336 | - Tue, 13 May 2025 09:11:13 GMT 337 | ETag: 338 | - W/"d3df58edfa45a886f808007ca3f954f6" 339 | Referrer-Policy: 340 | - strict-origin-when-cross-origin 341 | Server: 342 | - nginx 343 | Vary: 344 | - Accept 345 | X-Content-Type-Options: 346 | - nosniff 347 | X-Download-Options: 348 | - noopen 349 | X-Frame-Options: 350 | - SAMEORIGIN 351 | X-Permitted-Cross-Domain-Policies: 352 | - none 353 | X-Request-Id: 354 | - 654474a2-3e8c-49b4-868c-ab721e00cde7 355 | X-Runtime: 356 | - '0.483748' 357 | X-XSS-Protection: 358 | - 1; mode=block 359 | status: 360 | code: 201 361 | message: Created 362 | - request: 363 | body: null 364 | headers: 365 | Accept: 366 | - '*/*' 367 | Accept-Encoding: 368 | - gzip, deflate 369 | Authorization: 370 | - user 371 | Connection: 372 | - keep-alive 373 | User-Agent: 374 | - Zammad API Python 375 | method: GET 376 | uri: http://localhost:8080/api/v1/users?per_page=2&page=1&expand=true 377 | response: 378 | body: 379 | string: !!binary | 380 | H4sIAAAAAAAEA9WVzW+bMBTA/5XJZ4IgLEvKadqkHXfaqVVlGfOgVo2NbJOsqfK/7xmbhrSlm7RD 381 | VSkHv4+8L7+fuXkkoiZlnhBtWqbEkTmhFfU6NUiZEKlboUhJViQhjTDWKdZBlCV7EtEKHRMSLXgU 382 | HWvRKUQYBWr1YPiT7gBV8OzvtPLhfHT2Oxw6XQkZlTX0zLgOlAs26wxAPB9FH5RcuId40oNyJgqs 383 | rg1YGyx779wwaSEhezCiEYA9RgXjTuwxYxSVdjG975DGEYRuLvsYTbTBxn20DKc4OKob/DXCdxsD 384 | XmipddgSZdhFCHlpBVUv2gz0knHw45hdUW+gAQOKA/b6eErI0NfMQU2rh9ELL5cbWND4Msg6W29W 385 | 2WaVF7+yXVlsy7xId8XuGm9livU3P6MlYDas4OY2Ie6gcSrcaUPP1Z3Nz3Yt/okN7k6b8wpGtcbr 386 | 2gs44AoZJ1R7DtMaPfRB9G37Et7KP8WbLfprmSe3Z2mjesyJZ5/wPFWcoQdkGlblN3BFTkmga/0K 387 | XXgpE1pKcKw8rQwb1Ncj6zpWpzghj8QMuJ+jFypn1H3zf0HVhN5ypI9KpDMDAvuhgSRO8HtwlnKp 388 | bXwmJpXuAR/X/CWz6/l24YqPT/S0bwssfkk/b4o3mb0q87zMtun2amR7xmzx3tCS74N1ugNDliqZ 389 | uPwvfOcvDzJ6PdL26Qd+NvDJxC8fwvTvhC/hdrr9A6/qgJZXBwAA 390 | headers: 391 | Access-Control-Allow-Headers: 392 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 393 | X-File-Name, Cache-Control, Accept-Language 394 | Access-Control-Allow-Methods: 395 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 396 | Access-Control-Allow-Origin: 397 | - '*' 398 | Access-Control-Max-Age: 399 | - '1728000' 400 | Cache-Control: 401 | - max-age=0, private, must-revalidate 402 | Connection: 403 | - keep-alive 404 | Content-Encoding: 405 | - gzip 406 | Content-Security-Policy: 407 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 408 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 409 | ''self'' ''unsafe-eval'' ''nonce-MfktsHt+oejb4/npeU1k+g==''; style-src ''self'' 410 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 411 | Content-Type: 412 | - application/json; charset=utf-8 413 | Date: 414 | - Tue, 13 May 2025 09:11:13 GMT 415 | ETag: 416 | - W/"89dfbcc8569f02a8315bd2088761c6d8" 417 | Referrer-Policy: 418 | - strict-origin-when-cross-origin 419 | Server: 420 | - nginx 421 | Transfer-Encoding: 422 | - chunked 423 | Vary: 424 | - Accept 425 | X-Content-Type-Options: 426 | - nosniff 427 | X-Download-Options: 428 | - noopen 429 | X-Frame-Options: 430 | - SAMEORIGIN 431 | X-Permitted-Cross-Domain-Policies: 432 | - none 433 | X-Request-Id: 434 | - e7fca70f-a7e7-48b1-916d-14286080ad72 435 | X-Runtime: 436 | - '0.322245' 437 | X-XSS-Protection: 438 | - 1; mode=block 439 | status: 440 | code: 200 441 | message: OK 442 | - request: 443 | body: null 444 | headers: 445 | Accept: 446 | - '*/*' 447 | Accept-Encoding: 448 | - gzip, deflate 449 | Authorization: 450 | - user 451 | Connection: 452 | - keep-alive 453 | User-Agent: 454 | - Zammad API Python 455 | method: GET 456 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=2 457 | response: 458 | body: 459 | string: !!binary | 460 | H4sIAAAAAAAEA91WTW/bMAz9LzrbgR0nS+zTiu00DDt1lxWFIduUo8GWDFlO2gb57yP9na7G0GHD 461 | igE5UM+URD49krk7M5mxKHCYNjlX8olbqVVMmGqKwmGFzqViEfuuDyrT8B4eeFkVsEp1yRwmpKmt 462 | 4iWgxyf0QKjgI/JRAwJQclksniBLnuPu7rJ2Ede6MemInSDBzXhOddCK7kFT8IfOKHUiix7MoOLG 463 | lqDscFxtDQCuaMuTrDojlfaxt3SjrOkXPMsM1PWw9Ujughc1OOwIRgoJSEkP8NTKI95qTYOflbZ9 464 | BJR6PBC29tZb19u6fnDrhZG3jbzdautvvmEs1wm2G2KBJNEVHr5EY2Mt8Cck0dBfeoXGtcVcYz6m 465 | ev0VVLb4zUBV8BSIp9kzVwYEGFApIAVnyglTTjsxpFoJmRNccmskUn9mqQFOaZMlLRLEydYnBVmc 466 | PMYkiI6dEVI60RmS3cF1k9S4M6GUBxr7VC8OSw9cKUDRnAf19IepQpIGaHVBv6bK3kAUBkqpMjAx 467 | cpIeKKPXs9I/8pyWHlL69bxAnfKifbx/HQu9UqExGnw1Bsptaja+W6uUvvt0ehoRHzXQKqzTMZsX 468 | 0z7aBJG3Xr3z11RMnQZe8BuLbh2Qn9EFoOBR3ne+s753mD1prLrUahNP6u898POzfkj7EOWNPWgz 469 | tcke1tgjjhJO2LuMlSqfjsmNbqpueWY+HsIE9lV2jyxQRLSf3WQoIAzxJqfmtRTacNWsT78U1OD2 470 | LKIebsNB+8y+1mAInOIZGE+oJbozalvgpQFwcbrxsQl/4mtopEM7ROK0Czuxh3ATunuxztyN2IEb 471 | BjvheoLvEz8NeLDleK+YTZVbqC2FivBssnzGTvuFxs40XtD8X2dJNxzf/tT4jULHPx6D7GiYXRU6 472 | FvAuCvxVEIa/KvS536zQg6VamurzL5T5vLQ/NLXVJcp3KZKhXP9MVePVA52LRTu1zEWXy/0P4bXQ 473 | gRgKAAA= 474 | headers: 475 | Access-Control-Allow-Headers: 476 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 477 | X-File-Name, Cache-Control, Accept-Language 478 | Access-Control-Allow-Methods: 479 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 480 | Access-Control-Allow-Origin: 481 | - '*' 482 | Access-Control-Max-Age: 483 | - '1728000' 484 | Cache-Control: 485 | - max-age=0, private, must-revalidate 486 | Connection: 487 | - keep-alive 488 | Content-Encoding: 489 | - gzip 490 | Content-Security-Policy: 491 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 492 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 493 | ''self'' ''unsafe-eval'' ''nonce-wax3bu1srO73Nh0novPU/Q==''; style-src ''self'' 494 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 495 | Content-Type: 496 | - application/json; charset=utf-8 497 | Date: 498 | - Tue, 13 May 2025 09:11:13 GMT 499 | ETag: 500 | - W/"dbdba792161fb4c2fbc33ae4d5ad5735" 501 | Referrer-Policy: 502 | - strict-origin-when-cross-origin 503 | Server: 504 | - nginx 505 | Transfer-Encoding: 506 | - chunked 507 | Vary: 508 | - Accept 509 | X-Content-Type-Options: 510 | - nosniff 511 | X-Download-Options: 512 | - noopen 513 | X-Frame-Options: 514 | - SAMEORIGIN 515 | X-Permitted-Cross-Domain-Policies: 516 | - none 517 | X-Request-Id: 518 | - a39d506c-bf34-4d3c-bc7e-9304457c8a9c 519 | X-Runtime: 520 | - '0.387962' 521 | X-XSS-Protection: 522 | - 1; mode=block 523 | status: 524 | code: 200 525 | message: OK 526 | - request: 527 | body: null 528 | headers: 529 | Accept: 530 | - '*/*' 531 | Accept-Encoding: 532 | - gzip, deflate 533 | Authorization: 534 | - user 535 | Connection: 536 | - keep-alive 537 | User-Agent: 538 | - Zammad API Python 539 | method: GET 540 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=3 541 | response: 542 | body: 543 | string: !!binary | 544 | H4sIAAAAAAAEA+1UyW7bMBD9F55FQ5ItyfSpQK9FT+mlQSBQ4tBmIZECSdlJjPx7h1q8JBH6AQ3A 545 | w/DNm5UzfDwTJcguyyJi7J5r9cq9MroMoO6bJiKN2StNdoT33lCxkVkKIqeVAKCbnMV0G0ugUCV5 546 | EbOE5QknEZHKOq95C2j3AM7/cmATxBt+gX+g+DMwIgItVw0yUVQt36PRGHm4lM70tr5gJ6hGZncw 547 | OrhHI8mfR6E1lWomUEDHrW9B+9md8xYAb8HkVXWjUCv/Mkmm195OFy6EBedm02OgS944iMgRrJIK 548 | sD8TwGuvjhjV2x7V2vgpg1BsOXVvLOi+lEFVSqw9OIvxAXpfGolHqlDw5P4OLZ3Hqkp+KepeC1os 549 | 6ix0Da8hdOTmdTsLEizoGrDYMz52zYcOgqa9I28R6TvBPYiyehms1hGpLSwgIS2SxmlG44wm64eY 550 | 7Yazylj2G7s++1rkse3As6YBjIYZPa6fIuJPBttUe2PLa7qTHtXv5jZYIYrjejD2Os4TbPD5jgpO 551 | OFbWK72/utlb03fj9Yx1hxyCDfneO29asGQpk9nzzfp8lsNMe5fABA/RUQ6hrw3Gdv4xBy0MfINn 552 | 3nYNrGrT3nSyCvP6GeUtGhc7Tz40aB7qu8UuClbj0Oc0z3NBN+k6oVvGKpoKYAVkWcGysKrya7HH 553 | X+y/X+wk3sXFapsm/1jswNuu0jQPvK/Fnn7Oha29fpHLi/30F+AGbMOzBwAA 554 | headers: 555 | Access-Control-Allow-Headers: 556 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 557 | X-File-Name, Cache-Control, Accept-Language 558 | Access-Control-Allow-Methods: 559 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 560 | Access-Control-Allow-Origin: 561 | - '*' 562 | Access-Control-Max-Age: 563 | - '1728000' 564 | Cache-Control: 565 | - max-age=0, private, must-revalidate 566 | Connection: 567 | - keep-alive 568 | Content-Encoding: 569 | - gzip 570 | Content-Security-Policy: 571 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 572 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 573 | ''self'' ''unsafe-eval'' ''nonce-kPuZvq2sP+5sx75UUVV10A==''; style-src ''self'' 574 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 575 | Content-Type: 576 | - application/json; charset=utf-8 577 | Date: 578 | - Tue, 13 May 2025 09:11:14 GMT 579 | ETag: 580 | - W/"e0710aa838f0190fd9fbd9915dcab8bb" 581 | Referrer-Policy: 582 | - strict-origin-when-cross-origin 583 | Server: 584 | - nginx 585 | Transfer-Encoding: 586 | - chunked 587 | Vary: 588 | - Accept 589 | X-Content-Type-Options: 590 | - nosniff 591 | X-Download-Options: 592 | - noopen 593 | X-Frame-Options: 594 | - SAMEORIGIN 595 | X-Permitted-Cross-Domain-Policies: 596 | - none 597 | X-Request-Id: 598 | - 6c00df28-2a6e-4863-a89d-43479f82e364 599 | X-Runtime: 600 | - '0.277602' 601 | X-XSS-Protection: 602 | - 1; mode=block 603 | status: 604 | code: 200 605 | message: OK 606 | - request: 607 | body: null 608 | headers: 609 | Accept: 610 | - '*/*' 611 | Accept-Encoding: 612 | - gzip, deflate 613 | Authorization: 614 | - user 615 | Connection: 616 | - keep-alive 617 | User-Agent: 618 | - Zammad API Python 619 | method: GET 620 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=4 621 | response: 622 | body: 623 | string: !!binary | 624 | H4sIAAAAAAAEA+1UTW+jMBD9Lz5DBCEQktNKvVZ76l62qpCBMfEKbGSbpGnU/75jMIGkQXtfVeIw 625 | 8+Z7PI/XC+El2Sdbj0hVUcE/qOFSZBYUXV17pJYVF2RPaGekz3bhJtnS2E+ikvmbeBP5KUDqh1Dm 626 | cRqku5TtiEcYV9oI2gDGvYA2vzSoEPGaXuFnFH9aD49AQ3mNnijyhlYYNFTulUzLThVX7AT54Nke 627 | pLDpMYjR90FoZM5rB5bQUmUaEGZMp40CQM2GfPB2EApuzk6SnTDKKbQsFWg9hh6tO6O1Bo8cQXHG 628 | AffjAFoYfsSqRnVoFtK4DuywmdveMNDtKL0pYzi7TRbgA3Qmkww/xu3ALv0NmmmDU2X0OtStFUS5 629 | aFPQ1rQAu5HZ67YKGCgQBeCwF3zsgvYbBOF3mnx6pGtLaqDM8nMfFXmkULCA2LbIOljHfhD7YfQS 630 | 7PZhuA+S1XqT/satj7kW/eI0tH5K1oDVsKPX6M0j5iRxTYWRKpvadXY0392tjUIUz/Ug1XTODpb4 631 | fEcOJzwrZbiopjSVkl07qBec2/ZgY8hTp41sQJGlTsbMM/o86mF0u2vAwX11lG3pacG4zj/yIEoJ 632 | P+CdNm0Nq0I2s03m9l4fuXx6jtjplwWNRz0Suz0bpGhwV4HNKIwFZ8xFbWTs49BvEv9/JA7DVZBs 633 | /03iye+bxO4vucDQ6Xe4TOK3v5H6fb+fBwAA 634 | headers: 635 | Access-Control-Allow-Headers: 636 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 637 | X-File-Name, Cache-Control, Accept-Language 638 | Access-Control-Allow-Methods: 639 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 640 | Access-Control-Allow-Origin: 641 | - '*' 642 | Access-Control-Max-Age: 643 | - '1728000' 644 | Cache-Control: 645 | - max-age=0, private, must-revalidate 646 | Connection: 647 | - keep-alive 648 | Content-Encoding: 649 | - gzip 650 | Content-Security-Policy: 651 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 652 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 653 | ''self'' ''unsafe-eval'' ''nonce-5ztapBfvhTEZSTj4XF2YIQ==''; style-src ''self'' 654 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 655 | Content-Type: 656 | - application/json; charset=utf-8 657 | Date: 658 | - Tue, 13 May 2025 09:11:14 GMT 659 | ETag: 660 | - W/"bf1a3063bb299c2d13bbc656e4eb24e5" 661 | Referrer-Policy: 662 | - strict-origin-when-cross-origin 663 | Server: 664 | - nginx 665 | Transfer-Encoding: 666 | - chunked 667 | Vary: 668 | - Accept 669 | X-Content-Type-Options: 670 | - nosniff 671 | X-Download-Options: 672 | - noopen 673 | X-Frame-Options: 674 | - SAMEORIGIN 675 | X-Permitted-Cross-Domain-Policies: 676 | - none 677 | X-Request-Id: 678 | - 62cb8911-8efd-433c-904e-2cf6a9071c3a 679 | X-Runtime: 680 | - '0.292451' 681 | X-XSS-Protection: 682 | - 1; mode=block 683 | status: 684 | code: 200 685 | message: OK 686 | - request: 687 | body: null 688 | headers: 689 | Accept: 690 | - '*/*' 691 | Accept-Encoding: 692 | - gzip, deflate 693 | Authorization: 694 | - user 695 | Connection: 696 | - keep-alive 697 | User-Agent: 698 | - Zammad API Python 699 | method: GET 700 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=5 701 | response: 702 | body: 703 | string: !!binary | 704 | H4sIAAAAAAAEA+1UzW7bMAx+F52dwHaQdc1pwF5hpxWFodi0o0GWDIpKmgZ991K2HCdGjW3XooAO 705 | 4j/5iZ+eLkJVYvftMREWG2nUqyRlTRGUxmudCG0bZcROdGcCR9kPeJFtp2Fd2lYkolboyMgW2INF 706 | LW8laKXSi6GqlQ2HDVV6oXDWY3nVnWA/ZO0O1sQCtXwZdK3dKx2VFXQSqQVDYzpHCMBS6OlVdcOl 707 | VHSON+sNYRRkVSE4N4Yeg3sttYNEHAFVrYCxiApZkjpyVULPZmMpdhDmLiJSw0D3o/SmomY4QrKU 708 | wfZU2JpPrcLAMf2dtnDEUxXyOtS9FUy1aEPotCwhIHLzkh1CDQimBB72wg9byh5BMCvvxFsifFdJ 709 | gqrYn/uoTSJKhAVNaEvkab5dpdtVtvmVPu6yjM96u0l/M+pjrr/5odXA1bijp81zIuhkGaaSLBZT 710 | u9HO5tmOhijWSk8Hi9PqRrXl5zsqOPFaISnTTGkatL4bxAvPHXoIMeKnd2RbQLHUyZj5hiof9TC6 711 | zRqI6r4630PpCWCG8489mMrCjGIjkvuwrx+5vCUDiR/CXt10xgOOS31P4nxW4T9IPA/9IvEnJHG+ 712 | zvLv/0Diq98XieMvucDQ6TtcJvHzO7D/jHSLBwAA 713 | headers: 714 | Access-Control-Allow-Headers: 715 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 716 | X-File-Name, Cache-Control, Accept-Language 717 | Access-Control-Allow-Methods: 718 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 719 | Access-Control-Allow-Origin: 720 | - '*' 721 | Access-Control-Max-Age: 722 | - '1728000' 723 | Cache-Control: 724 | - max-age=0, private, must-revalidate 725 | Connection: 726 | - keep-alive 727 | Content-Encoding: 728 | - gzip 729 | Content-Security-Policy: 730 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 731 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 732 | ''self'' ''unsafe-eval'' ''nonce-7Ub4bhEe3CdsgpQZyNyh2g==''; style-src ''self'' 733 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 734 | Content-Type: 735 | - application/json; charset=utf-8 736 | Date: 737 | - Tue, 13 May 2025 09:11:14 GMT 738 | ETag: 739 | - W/"80cea4efbf2db89af54cd858b01d4c91" 740 | Referrer-Policy: 741 | - strict-origin-when-cross-origin 742 | Server: 743 | - nginx 744 | Transfer-Encoding: 745 | - chunked 746 | Vary: 747 | - Accept 748 | X-Content-Type-Options: 749 | - nosniff 750 | X-Download-Options: 751 | - noopen 752 | X-Frame-Options: 753 | - SAMEORIGIN 754 | X-Permitted-Cross-Domain-Policies: 755 | - none 756 | X-Request-Id: 757 | - 02fd1f14-00c2-439d-8c56-76cdef98ba70 758 | X-Runtime: 759 | - '0.255968' 760 | X-XSS-Protection: 761 | - 1; mode=block 762 | status: 763 | code: 200 764 | message: OK 765 | - request: 766 | body: null 767 | headers: 768 | Accept: 769 | - '*/*' 770 | Accept-Encoding: 771 | - gzip, deflate 772 | Authorization: 773 | - user 774 | Connection: 775 | - keep-alive 776 | User-Agent: 777 | - Zammad API Python 778 | method: GET 779 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=6 780 | response: 781 | body: 782 | string: !!binary | 783 | H4sIAAAAAAAEA+1UTY+bMBD9Lz6TCMJm2+ZUqX+hp65WyIGBuDIeZI+TzUb73zsGExK6qHuuVuLg 784 | efP97MfTRahK7L5kiUDbSKNeJSk0RQCN1zoRGhtlxE50ZwJH+Xd4kW2nYV1iKxJRK+vIyBY4gk0t 785 | by1opdKLqaqVDacNXXqjcOhtecVOsB+qdgc0sUEtXwasxb3SEaygk5ZaMDSWc2QB2AozvapuOJSK 786 | zvGE3pCNhqwqC86NqccQXkvtIBFHsKpWwFxEQJakjtyVrGe3QYoThL2LyNSw0P0qvauomY5QLGWy 787 | PRVY81ersHAsf4cWjnirQl6XuveCqRZ9FjotSwiM3NxkZ6EGC6YEXvbCF1vKnkEwK+/EWyJ8V0mC 788 | qtif+6w8EaWFBSSMJTbpZrtKt6ss/5l+22XZLtust18ffzHrY61/xVnUwN14oqf8ORF0QqapJLTF 789 | NG70s3v2RkMWo9LTAe30dCOMfH1HBSd+VpaUaaYyjUXfDeaF9w4zhBzxwzvCFqxYmmSsfCOV92YY 790 | w2YDRLjvzufQeiKY6fyNB1MhzCQ2MrkP7/W9kLckinjzF0Hjo74X8cOsQ/1xEc9TP0X8H4o4X6eP 791 | Dx8Q8TXuU8TxL7mg0Ol3uCzi5z/MsuiHiwcAAA== 792 | headers: 793 | Access-Control-Allow-Headers: 794 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 795 | X-File-Name, Cache-Control, Accept-Language 796 | Access-Control-Allow-Methods: 797 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 798 | Access-Control-Allow-Origin: 799 | - '*' 800 | Access-Control-Max-Age: 801 | - '1728000' 802 | Cache-Control: 803 | - max-age=0, private, must-revalidate 804 | Connection: 805 | - keep-alive 806 | Content-Encoding: 807 | - gzip 808 | Content-Security-Policy: 809 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 810 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 811 | ''self'' ''unsafe-eval'' ''nonce-wpWRbpq1VUX6YFp/czCF+g==''; style-src ''self'' 812 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 813 | Content-Type: 814 | - application/json; charset=utf-8 815 | Date: 816 | - Tue, 13 May 2025 09:11:15 GMT 817 | ETag: 818 | - W/"1e754e504211f82b15393d348ac3f0e0" 819 | Referrer-Policy: 820 | - strict-origin-when-cross-origin 821 | Server: 822 | - nginx 823 | Transfer-Encoding: 824 | - chunked 825 | Vary: 826 | - Accept 827 | X-Content-Type-Options: 828 | - nosniff 829 | X-Download-Options: 830 | - noopen 831 | X-Frame-Options: 832 | - SAMEORIGIN 833 | X-Permitted-Cross-Domain-Policies: 834 | - none 835 | X-Request-Id: 836 | - 9462245a-5022-40eb-88de-c34be145df3d 837 | X-Runtime: 838 | - '0.276981' 839 | X-XSS-Protection: 840 | - 1; mode=block 841 | status: 842 | code: 200 843 | message: OK 844 | - request: 845 | body: null 846 | headers: 847 | Accept: 848 | - '*/*' 849 | Accept-Encoding: 850 | - gzip, deflate 851 | Authorization: 852 | - user 853 | Connection: 854 | - keep-alive 855 | User-Agent: 856 | - Zammad API Python 857 | method: GET 858 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=7 859 | response: 860 | body: 861 | string: '[]' 862 | headers: 863 | Access-Control-Allow-Headers: 864 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 865 | X-File-Name, Cache-Control, Accept-Language 866 | Access-Control-Allow-Methods: 867 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 868 | Access-Control-Allow-Origin: 869 | - '*' 870 | Access-Control-Max-Age: 871 | - '1728000' 872 | Cache-Control: 873 | - max-age=0, private, must-revalidate 874 | Connection: 875 | - keep-alive 876 | Content-Length: 877 | - '2' 878 | Content-Security-Policy: 879 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 880 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 881 | ''self'' ''unsafe-eval'' ''nonce-fQo18Nleza8K9chrSdqj8g==''; style-src ''self'' 882 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 883 | Content-Type: 884 | - application/json; charset=utf-8 885 | Date: 886 | - Tue, 13 May 2025 09:11:15 GMT 887 | ETag: 888 | - W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" 889 | Referrer-Policy: 890 | - strict-origin-when-cross-origin 891 | Server: 892 | - nginx 893 | Vary: 894 | - Accept 895 | X-Content-Type-Options: 896 | - nosniff 897 | X-Download-Options: 898 | - noopen 899 | X-Frame-Options: 900 | - SAMEORIGIN 901 | X-Permitted-Cross-Domain-Policies: 902 | - none 903 | X-Request-Id: 904 | - 7571a047-3529-4eea-befb-909a29e12294 905 | X-Runtime: 906 | - '0.217229' 907 | X-XSS-Protection: 908 | - 1; mode=block 909 | status: 910 | code: 200 911 | message: OK 912 | - request: 913 | body: null 914 | headers: 915 | Accept: 916 | - '*/*' 917 | Accept-Encoding: 918 | - gzip, deflate 919 | Authorization: 920 | - user 921 | Connection: 922 | - keep-alive 923 | User-Agent: 924 | - Zammad API Python 925 | method: GET 926 | uri: http://localhost:8080/api/v1/users?per_page=2&expand=true&page=6 927 | response: 928 | body: 929 | string: !!binary | 930 | H4sIAAAAAAAEA+1UTY+bMBD9Lz6TCMJm2+ZUqX+hp65WyIGBuDIeZI+TzUb73zsGExK6qHuuVuLg 931 | efP97MfTRahK7L5kiUDbSKNeJSk0RQCN1zoRGhtlxE50ZwJH+Xd4kW2nYV1iKxJRK+vIyBY4gk0t 932 | by1opdKLqaqVDacNXXqjcOhtecVOsB+qdgc0sUEtXwasxb3SEaygk5ZaMDSWc2QB2AozvapuOJSK 933 | zvGE3pCNhqwqC86NqccQXkvtIBFHsKpWwFxEQJakjtyVrGe3QYoThL2LyNSw0P0qvauomY5QLGWy 934 | PRVY81ersHAsf4cWjnirQl6XuveCqRZ9FjotSwiM3NxkZ6EGC6YEXvbCF1vKnkEwK+/EWyJ8V0mC 935 | qtif+6w8EaWFBSSMJTbpZrtKt6ss/5l+22XZLtust18ffzHrY61/xVnUwN14oqf8ORF0QqapJLTF 936 | NG70s3v2RkMWo9LTAe30dCOMfH1HBSd+VpaUaaYyjUXfDeaF9w4zhBzxwzvCFqxYmmSsfCOV92YY 937 | w2YDRLjvzufQeiKY6fyNB1MhzCQ2MrkP7/W9kLckinjzF0Hjo74X8cOsQ/1xEc9TP0X8H4o4X6eP 938 | Dx8Q8TXuU8TxL7mg0Ol3uCzi5z/MsuiHiwcAAA== 939 | headers: 940 | Access-Control-Allow-Headers: 941 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 942 | X-File-Name, Cache-Control, Accept-Language 943 | Access-Control-Allow-Methods: 944 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 945 | Access-Control-Allow-Origin: 946 | - '*' 947 | Access-Control-Max-Age: 948 | - '1728000' 949 | Cache-Control: 950 | - max-age=0, private, must-revalidate 951 | Connection: 952 | - keep-alive 953 | Content-Encoding: 954 | - gzip 955 | Content-Security-Policy: 956 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 957 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 958 | ''self'' ''unsafe-eval'' ''nonce-lIDNiabdrq2fpKyjFwo/gg==''; style-src ''self'' 959 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 960 | Content-Type: 961 | - application/json; charset=utf-8 962 | Date: 963 | - Tue, 13 May 2025 09:11:15 GMT 964 | ETag: 965 | - W/"1e754e504211f82b15393d348ac3f0e0" 966 | Referrer-Policy: 967 | - strict-origin-when-cross-origin 968 | Server: 969 | - nginx 970 | Transfer-Encoding: 971 | - chunked 972 | Vary: 973 | - Accept 974 | X-Content-Type-Options: 975 | - nosniff 976 | X-Download-Options: 977 | - noopen 978 | X-Frame-Options: 979 | - SAMEORIGIN 980 | X-Permitted-Cross-Domain-Policies: 981 | - none 982 | X-Request-Id: 983 | - 3bcb5590-0f11-4c45-b3e3-ffff21d01c13 984 | X-Runtime: 985 | - '0.271618' 986 | X-XSS-Protection: 987 | - 1; mode=block 988 | status: 989 | code: 200 990 | message: OK 991 | - request: 992 | body: null 993 | headers: 994 | Accept: 995 | - '*/*' 996 | Accept-Encoding: 997 | - gzip, deflate 998 | Authorization: 999 | - user 1000 | Connection: 1001 | - keep-alive 1002 | Content-Length: 1003 | - '0' 1004 | User-Agent: 1005 | - Zammad API Python 1006 | method: DELETE 1007 | uri: http://localhost:8080/api/v1/users/68 1008 | response: 1009 | body: 1010 | string: '{}' 1011 | headers: 1012 | Access-Control-Allow-Headers: 1013 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 1014 | X-File-Name, Cache-Control, Accept-Language 1015 | Access-Control-Allow-Methods: 1016 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 1017 | Access-Control-Allow-Origin: 1018 | - '*' 1019 | Access-Control-Max-Age: 1020 | - '1728000' 1021 | Cache-Control: 1022 | - max-age=0, private, must-revalidate 1023 | Connection: 1024 | - keep-alive 1025 | Content-Length: 1026 | - '2' 1027 | Content-Security-Policy: 1028 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 1029 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 1030 | ''self'' ''unsafe-eval'' ''nonce-2D1/3qy3rSA91nRB0a8wPQ==''; style-src ''self'' 1031 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 1032 | Content-Type: 1033 | - application/json; charset=utf-8 1034 | Date: 1035 | - Tue, 13 May 2025 09:11:16 GMT 1036 | ETag: 1037 | - W/"44136fa355b3678a1146ad16f7e8649e" 1038 | Referrer-Policy: 1039 | - strict-origin-when-cross-origin 1040 | Server: 1041 | - nginx 1042 | Vary: 1043 | - Accept 1044 | X-Content-Type-Options: 1045 | - nosniff 1046 | X-Download-Options: 1047 | - noopen 1048 | X-Frame-Options: 1049 | - SAMEORIGIN 1050 | X-Permitted-Cross-Domain-Policies: 1051 | - none 1052 | X-Request-Id: 1053 | - bc36298c-cf5b-42d4-a5ba-7729b0039b48 1054 | X-Runtime: 1055 | - '0.656487' 1056 | X-XSS-Protection: 1057 | - 1; mode=block 1058 | status: 1059 | code: 200 1060 | message: OK 1061 | - request: 1062 | body: null 1063 | headers: 1064 | Accept: 1065 | - '*/*' 1066 | Accept-Encoding: 1067 | - gzip, deflate 1068 | Authorization: 1069 | - user 1070 | Connection: 1071 | - keep-alive 1072 | Content-Length: 1073 | - '0' 1074 | User-Agent: 1075 | - Zammad API Python 1076 | method: DELETE 1077 | uri: http://localhost:8080/api/v1/users/69 1078 | response: 1079 | body: 1080 | string: '{}' 1081 | headers: 1082 | Access-Control-Allow-Headers: 1083 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 1084 | X-File-Name, Cache-Control, Accept-Language 1085 | Access-Control-Allow-Methods: 1086 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 1087 | Access-Control-Allow-Origin: 1088 | - '*' 1089 | Access-Control-Max-Age: 1090 | - '1728000' 1091 | Cache-Control: 1092 | - max-age=0, private, must-revalidate 1093 | Connection: 1094 | - keep-alive 1095 | Content-Length: 1096 | - '2' 1097 | Content-Security-Policy: 1098 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 1099 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 1100 | ''self'' ''unsafe-eval'' ''nonce-6+D/8WpB/WtBekNK4BCgMw==''; style-src ''self'' 1101 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 1102 | Content-Type: 1103 | - application/json; charset=utf-8 1104 | Date: 1105 | - Tue, 13 May 2025 09:11:16 GMT 1106 | ETag: 1107 | - W/"44136fa355b3678a1146ad16f7e8649e" 1108 | Referrer-Policy: 1109 | - strict-origin-when-cross-origin 1110 | Server: 1111 | - nginx 1112 | Vary: 1113 | - Accept 1114 | X-Content-Type-Options: 1115 | - nosniff 1116 | X-Download-Options: 1117 | - noopen 1118 | X-Frame-Options: 1119 | - SAMEORIGIN 1120 | X-Permitted-Cross-Domain-Policies: 1121 | - none 1122 | X-Request-Id: 1123 | - 277f8c88-39ec-4b54-851f-28dac8594b63 1124 | X-Runtime: 1125 | - '0.684147' 1126 | X-XSS-Protection: 1127 | - 1; mode=block 1128 | status: 1129 | code: 200 1130 | message: OK 1131 | - request: 1132 | body: null 1133 | headers: 1134 | Accept: 1135 | - '*/*' 1136 | Accept-Encoding: 1137 | - gzip, deflate 1138 | Authorization: 1139 | - user 1140 | Connection: 1141 | - keep-alive 1142 | Content-Length: 1143 | - '0' 1144 | User-Agent: 1145 | - Zammad API Python 1146 | method: DELETE 1147 | uri: http://localhost:8080/api/v1/users/70 1148 | response: 1149 | body: 1150 | string: '{}' 1151 | headers: 1152 | Access-Control-Allow-Headers: 1153 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 1154 | X-File-Name, Cache-Control, Accept-Language 1155 | Access-Control-Allow-Methods: 1156 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 1157 | Access-Control-Allow-Origin: 1158 | - '*' 1159 | Access-Control-Max-Age: 1160 | - '1728000' 1161 | Cache-Control: 1162 | - max-age=0, private, must-revalidate 1163 | Connection: 1164 | - keep-alive 1165 | Content-Length: 1166 | - '2' 1167 | Content-Security-Policy: 1168 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 1169 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 1170 | ''self'' ''unsafe-eval'' ''nonce-YCMUUnXLF2uJal3fSeJnnA==''; style-src ''self'' 1171 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 1172 | Content-Type: 1173 | - application/json; charset=utf-8 1174 | Date: 1175 | - Tue, 13 May 2025 09:11:17 GMT 1176 | ETag: 1177 | - W/"44136fa355b3678a1146ad16f7e8649e" 1178 | Referrer-Policy: 1179 | - strict-origin-when-cross-origin 1180 | Server: 1181 | - nginx 1182 | Vary: 1183 | - Accept 1184 | X-Content-Type-Options: 1185 | - nosniff 1186 | X-Download-Options: 1187 | - noopen 1188 | X-Frame-Options: 1189 | - SAMEORIGIN 1190 | X-Permitted-Cross-Domain-Policies: 1191 | - none 1192 | X-Request-Id: 1193 | - adaa26f1-7be6-41ca-9930-12a3254c461d 1194 | X-Runtime: 1195 | - '0.684808' 1196 | X-XSS-Protection: 1197 | - 1; mode=block 1198 | status: 1199 | code: 200 1200 | message: OK 1201 | - request: 1202 | body: null 1203 | headers: 1204 | Accept: 1205 | - '*/*' 1206 | Accept-Encoding: 1207 | - gzip, deflate 1208 | Authorization: 1209 | - user 1210 | Connection: 1211 | - keep-alive 1212 | Content-Length: 1213 | - '0' 1214 | User-Agent: 1215 | - Zammad API Python 1216 | method: DELETE 1217 | uri: http://localhost:8080/api/v1/users/71 1218 | response: 1219 | body: 1220 | string: '{}' 1221 | headers: 1222 | Access-Control-Allow-Headers: 1223 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 1224 | X-File-Name, Cache-Control, Accept-Language 1225 | Access-Control-Allow-Methods: 1226 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 1227 | Access-Control-Allow-Origin: 1228 | - '*' 1229 | Access-Control-Max-Age: 1230 | - '1728000' 1231 | Cache-Control: 1232 | - max-age=0, private, must-revalidate 1233 | Connection: 1234 | - keep-alive 1235 | Content-Length: 1236 | - '2' 1237 | Content-Security-Policy: 1238 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 1239 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 1240 | ''self'' ''unsafe-eval'' ''nonce-OZXTiTgASbb33uThhq9xSw==''; style-src ''self'' 1241 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 1242 | Content-Type: 1243 | - application/json; charset=utf-8 1244 | Date: 1245 | - Tue, 13 May 2025 09:11:18 GMT 1246 | ETag: 1247 | - W/"44136fa355b3678a1146ad16f7e8649e" 1248 | Referrer-Policy: 1249 | - strict-origin-when-cross-origin 1250 | Server: 1251 | - nginx 1252 | Vary: 1253 | - Accept 1254 | X-Content-Type-Options: 1255 | - nosniff 1256 | X-Download-Options: 1257 | - noopen 1258 | X-Frame-Options: 1259 | - SAMEORIGIN 1260 | X-Permitted-Cross-Domain-Policies: 1261 | - none 1262 | X-Request-Id: 1263 | - ea5dff8c-453f-4215-ab99-b1e9df8cf1a3 1264 | X-Runtime: 1265 | - '0.707198' 1266 | X-XSS-Protection: 1267 | - 1; mode=block 1268 | status: 1269 | code: 200 1270 | message: OK 1271 | - request: 1272 | body: null 1273 | headers: 1274 | Accept: 1275 | - '*/*' 1276 | Accept-Encoding: 1277 | - gzip, deflate 1278 | Authorization: 1279 | - user 1280 | Connection: 1281 | - keep-alive 1282 | Content-Length: 1283 | - '0' 1284 | User-Agent: 1285 | - Zammad API Python 1286 | method: DELETE 1287 | uri: http://localhost:8080/api/v1/users/72 1288 | response: 1289 | body: 1290 | string: '{}' 1291 | headers: 1292 | Access-Control-Allow-Headers: 1293 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 1294 | X-File-Name, Cache-Control, Accept-Language 1295 | Access-Control-Allow-Methods: 1296 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 1297 | Access-Control-Allow-Origin: 1298 | - '*' 1299 | Access-Control-Max-Age: 1300 | - '1728000' 1301 | Cache-Control: 1302 | - max-age=0, private, must-revalidate 1303 | Connection: 1304 | - keep-alive 1305 | Content-Length: 1306 | - '2' 1307 | Content-Security-Policy: 1308 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 1309 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 1310 | ''self'' ''unsafe-eval'' ''nonce-pzRqz2GpyFaxgqfNWUX0JQ==''; style-src ''self'' 1311 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 1312 | Content-Type: 1313 | - application/json; charset=utf-8 1314 | Date: 1315 | - Tue, 13 May 2025 09:11:19 GMT 1316 | ETag: 1317 | - W/"44136fa355b3678a1146ad16f7e8649e" 1318 | Referrer-Policy: 1319 | - strict-origin-when-cross-origin 1320 | Server: 1321 | - nginx 1322 | Vary: 1323 | - Accept 1324 | X-Content-Type-Options: 1325 | - nosniff 1326 | X-Download-Options: 1327 | - noopen 1328 | X-Frame-Options: 1329 | - SAMEORIGIN 1330 | X-Permitted-Cross-Domain-Policies: 1331 | - none 1332 | X-Request-Id: 1333 | - 10211dad-4ad2-4f6d-8623-cba71ea53650 1334 | X-Runtime: 1335 | - '0.686239' 1336 | X-XSS-Protection: 1337 | - 1; mode=block 1338 | status: 1339 | code: 200 1340 | message: OK 1341 | version: 1 1342 | -------------------------------------------------------------------------------- /tests/cassettes/TestAPI.test_tickets.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Authorization: 10 | - user 11 | Connection: 12 | - keep-alive 13 | User-Agent: 14 | - Zammad API Python 15 | method: GET 16 | uri: http://localhost:8080/api/v1/tickets?page=1&per_page=10&expand=true 17 | response: 18 | body: 19 | string: !!binary | 20 | H4sIAAAAAAAEA4VU207jMBD9leLntEpSSlCeVkLiC3aFBEKW60y71jrjyHaEAPHvjOM4kCpZ3uKZ 21 | M7czZ/L0zlTD6iJjZ2v6jo+PzipjlX8d3mXGnBcektPYs0D1JrwymGzYt0ewrGZVlecFy5hXXgO9 22 | H0BL08LGm82jaFvRXJHTvCDYFCp75wkR31QLjadA7LXO2ElZ57kF1xl0wIVfcYCTQseGVjEKeatw 23 | JUGjTic+R0htZiXje7FSdC2FL+TtuyaQuZho9M0zjcaFVFoQO5d9RqNBL6QfGGNlXh62+WFb7H/n 24 | t/W+qoubXbW/eaRNzMFnwBgS2Z85pzUFgn9MGTc8dv61EmkhzC6sV1ID969dVNUhYxcuB9h8aSIF 25 | SNMjlSe5LtLXUZDCM/eqnSQUaqSlBzvvUU0y6iycwAJKcKx+/8hY7Ljhx0n6sa8lyyoRh6oM3KZc 26 | q7jb6+uAS8Ophpp4Kp7D8ch/4IcxuJDD0GGsCCD/cKu0hT8OrKMM3y+SzPHQNvdEVjMcBUGWU1I9 27 | SjdcN8UhvBAy3T4Zyg0a2wodSoSTJdOWvpMUQoiSRsPuaEWPv96GA99RNwE0rDrw9l9YIukH2IU6 28 | 4lJZ99cgTLUmWUXtUNm78dfCPp4/AYGNcQjpBAAA 29 | headers: 30 | Access-Control-Allow-Headers: 31 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 32 | X-File-Name, Cache-Control, Accept-Language 33 | Access-Control-Allow-Methods: 34 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 35 | Access-Control-Allow-Origin: 36 | - '*' 37 | Access-Control-Max-Age: 38 | - '1728000' 39 | Cache-Control: 40 | - max-age=0, private, must-revalidate 41 | Connection: 42 | - keep-alive 43 | Content-Encoding: 44 | - gzip 45 | Content-Security-Policy: 46 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 47 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 48 | ''self'' ''unsafe-eval'' ''nonce-/j0/Csz5Q1uvFLDALKsn5Q==''; style-src ''self'' 49 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 50 | Content-Type: 51 | - application/json; charset=utf-8 52 | Date: 53 | - Tue, 13 May 2025 09:11:07 GMT 54 | ETag: 55 | - W/"e675291e678242b6c750376e8954e2e2" 56 | Referrer-Policy: 57 | - strict-origin-when-cross-origin 58 | Server: 59 | - nginx 60 | Transfer-Encoding: 61 | - chunked 62 | Vary: 63 | - Accept 64 | X-Content-Type-Options: 65 | - nosniff 66 | X-Download-Options: 67 | - noopen 68 | X-Frame-Options: 69 | - SAMEORIGIN 70 | X-Permitted-Cross-Domain-Policies: 71 | - none 72 | X-Request-Id: 73 | - cbd2a729-4c9e-44f0-9514-fc1ac88ee388 74 | X-Runtime: 75 | - '0.233386' 76 | X-XSS-Protection: 77 | - 1; mode=block 78 | status: 79 | code: 200 80 | message: OK 81 | - request: 82 | body: null 83 | headers: 84 | Accept: 85 | - '*/*' 86 | Accept-Encoding: 87 | - gzip, deflate 88 | Authorization: 89 | - user 90 | Connection: 91 | - keep-alive 92 | User-Agent: 93 | - Zammad API Python 94 | method: GET 95 | uri: http://localhost:8080/api/v1/tickets/1 96 | response: 97 | body: 98 | string: !!binary | 99 | H4sIAAAAAAAEA4VS0W6DMAz8lS7PdAJaSsV3TJrUlygNBkUKTpQYTV3Vf19ogIoKtEf7fGf77DtT 100 | NauyhLXO9JaPgXXKOEW3Z5wnzJMgmEDjWoHqV5AyOOWw767gWMXKMk0zljBSpCHE36Cl6WBHZncR 101 | XSfqjwCaHwQ3UWXvKVTEOPRCQ4GIvdYJa5TzxB14a9ADF7QBgJdCx4E2axTyTuGGQK2ahi8rpDaL 102 | ljFe7RShNfqKbm/rwcxVoRFbKo3JFSktgjvvc8akQRKSno6xPM2LfVrss8NXeq4OZZWdPsvD6RIu 103 | sSxuASMlur8A5zMNBv8rGS88Tv46iXQw7C4cKamB083GryoS9gZ5wPr1ExNBmh5D+/Cuq/bZQFLY 104 | clLd/EJDj+noQ573qOY3sg4acIASPKvuj4TFiWt+nV8/zrWW2TSiKPPB20lrs+58PF7Y4w+4P84C 105 | ggMAAA== 106 | headers: 107 | Access-Control-Allow-Headers: 108 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 109 | X-File-Name, Cache-Control, Accept-Language 110 | Access-Control-Allow-Methods: 111 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 112 | Access-Control-Allow-Origin: 113 | - '*' 114 | Access-Control-Max-Age: 115 | - '1728000' 116 | Cache-Control: 117 | - max-age=0, private, must-revalidate 118 | Connection: 119 | - keep-alive 120 | Content-Encoding: 121 | - gzip 122 | Content-Security-Policy: 123 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 124 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 125 | ''self'' ''unsafe-eval'' ''nonce-pU5wLcxpBo8+B2X9m7V6Iw==''; style-src ''self'' 126 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 127 | Content-Type: 128 | - application/json; charset=utf-8 129 | Date: 130 | - Tue, 13 May 2025 09:11:07 GMT 131 | ETag: 132 | - W/"76fa325db64d127733b6c48b763b9aff" 133 | Referrer-Policy: 134 | - strict-origin-when-cross-origin 135 | Server: 136 | - nginx 137 | Transfer-Encoding: 138 | - chunked 139 | Vary: 140 | - Accept 141 | X-Content-Type-Options: 142 | - nosniff 143 | X-Download-Options: 144 | - noopen 145 | X-Frame-Options: 146 | - SAMEORIGIN 147 | X-Permitted-Cross-Domain-Policies: 148 | - none 149 | X-Request-Id: 150 | - 37e5bce1-27c9-4810-80e9-0503e2aad46f 151 | X-Runtime: 152 | - '0.206436' 153 | X-XSS-Protection: 154 | - 1; mode=block 155 | status: 156 | code: 200 157 | message: OK 158 | - request: 159 | body: '{"title": "some new title", "state": "new", "priority": "2 normal", "owner": 160 | "-", "customer": "nicole.braun@zammad.org", "group": "Users", "article": {"sender": 161 | "Customer", "type": "note", "subject": "some subject", "content_type": "text/plain", 162 | "body": "some body\nnext line"}}' 163 | headers: 164 | Accept: 165 | - '*/*' 166 | Accept-Encoding: 167 | - gzip, deflate 168 | Authorization: 169 | - user 170 | Connection: 171 | - keep-alive 172 | Content-Length: 173 | - '278' 174 | Content-Type: 175 | - application/json 176 | User-Agent: 177 | - Zammad API Python 178 | method: POST 179 | uri: http://localhost:8080/api/v1/tickets 180 | response: 181 | body: 182 | string: '{"id":5,"group_id":1,"priority_id":2,"state_id":1,"organization_id":1,"number":"77005","title":"some 183 | new title","owner_id":1,"customer_id":2,"note":null,"first_response_at":null,"first_response_escalation_at":null,"first_response_in_min":null,"first_response_diff_in_min":null,"close_at":null,"close_escalation_at":null,"close_in_min":null,"close_diff_in_min":null,"update_escalation_at":null,"update_in_min":null,"update_diff_in_min":null,"last_close_at":null,"last_contact_at":null,"last_contact_agent_at":null,"last_contact_customer_at":null,"last_owner_update_at":null,"create_article_type_id":10,"create_article_sender_id":2,"article_count":1,"escalation_at":null,"pending_time":null,"type":null,"time_unit":null,"preferences":{},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:07.694Z","updated_at":"2025-05-13T09:11:08.021Z","article_ids":[5],"ticket_time_accounting_ids":[]}' 184 | headers: 185 | Access-Control-Allow-Headers: 186 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 187 | X-File-Name, Cache-Control, Accept-Language 188 | Access-Control-Allow-Methods: 189 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 190 | Access-Control-Allow-Origin: 191 | - '*' 192 | Access-Control-Max-Age: 193 | - '1728000' 194 | Cache-Control: 195 | - max-age=0, private, must-revalidate 196 | Connection: 197 | - keep-alive 198 | Content-Length: 199 | - '901' 200 | Content-Security-Policy: 201 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 202 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 203 | ''self'' ''unsafe-eval'' ''nonce-J4gNBjbox88oZDm/pwkoMQ==''; style-src ''self'' 204 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 205 | Content-Type: 206 | - application/json; charset=utf-8 207 | Date: 208 | - Tue, 13 May 2025 09:11:08 GMT 209 | ETag: 210 | - W/"47d74e1ae3535475fc26e36480312396" 211 | Referrer-Policy: 212 | - strict-origin-when-cross-origin 213 | Server: 214 | - nginx 215 | Vary: 216 | - Accept 217 | X-Content-Type-Options: 218 | - nosniff 219 | X-Download-Options: 220 | - noopen 221 | X-Frame-Options: 222 | - SAMEORIGIN 223 | X-Permitted-Cross-Domain-Policies: 224 | - none 225 | X-Request-Id: 226 | - d1f58598-8818-4acb-a701-35131541a754 227 | X-Runtime: 228 | - '0.600873' 229 | X-XSS-Protection: 230 | - 1; mode=block 231 | status: 232 | code: 201 233 | message: Created 234 | - request: 235 | body: '{"title": "TestTicket1"}' 236 | headers: 237 | Accept: 238 | - '*/*' 239 | Accept-Encoding: 240 | - gzip, deflate 241 | Authorization: 242 | - user 243 | Connection: 244 | - keep-alive 245 | Content-Length: 246 | - '24' 247 | Content-Type: 248 | - application/json 249 | User-Agent: 250 | - Zammad API Python 251 | method: PUT 252 | uri: http://localhost:8080/api/v1/tickets/5 253 | response: 254 | body: 255 | string: !!binary | 256 | H4sIAAAAAAAEA3VT0W6DMAz8lzyzCuhYW76jT5uqKA0usgYOSoymruq/LyHQjgoefWc7Z/tyE1iJ 257 | skhEbU3fyRBkiegsGot8HeI8EY4Vw0QaWyvCX8VoaMKob89gRSl2uzQtRCIYuQEfH8HxEfU3cOZR 258 | 80NgpxrdOzbtGPtHyLCvoL5pEnFB61hacJ0hB1LxCgFOqyYqWc1Bki3SSoMKLxc5z9CNmT0Z48WX 259 | IrVUvtC376qwxcVGIzfvNIILrRrlt/OqM4KGWGn+t7E5XAOtko+DPFc5FMerjWqenLYQ5lGWUTcg 260 | +dqNFkkT8cI5oOp56KlCm578Yb3fFnfS+SKkWjK2D1+ER6ZLBlz2hA9vdBYuYIE0OFHe7omIkit5 261 | jkbeTrqWkDCXyNO8eEuLt2x7TA9llpXpbvNxeP/01p16reTtN/k+C3nTcFh5EV/FKXyFYP9hDKn0 262 | MHQYKyac7n/FPpq/ggMAAA== 263 | headers: 264 | Access-Control-Allow-Headers: 265 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 266 | X-File-Name, Cache-Control, Accept-Language 267 | Access-Control-Allow-Methods: 268 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 269 | Access-Control-Allow-Origin: 270 | - '*' 271 | Access-Control-Max-Age: 272 | - '1728000' 273 | Cache-Control: 274 | - max-age=0, private, must-revalidate 275 | Connection: 276 | - keep-alive 277 | Content-Encoding: 278 | - gzip 279 | Content-Security-Policy: 280 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 281 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 282 | ''self'' ''unsafe-eval'' ''nonce-IsRXgafID8iowFeRiGW/ZA==''; style-src ''self'' 283 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 284 | Content-Type: 285 | - application/json; charset=utf-8 286 | Date: 287 | - Tue, 13 May 2025 09:11:08 GMT 288 | ETag: 289 | - W/"eabcd12959400951ff089d69a00a2a89" 290 | Referrer-Policy: 291 | - strict-origin-when-cross-origin 292 | Server: 293 | - nginx 294 | Transfer-Encoding: 295 | - chunked 296 | Vary: 297 | - Accept 298 | X-Content-Type-Options: 299 | - nosniff 300 | X-Download-Options: 301 | - noopen 302 | X-Frame-Options: 303 | - SAMEORIGIN 304 | X-Permitted-Cross-Domain-Policies: 305 | - none 306 | X-Request-Id: 307 | - bfd397cf-cba2-4ba9-8962-8e7a7ba6e2d3 308 | X-Runtime: 309 | - '0.374733' 310 | X-XSS-Protection: 311 | - 1; mode=block 312 | status: 313 | code: 200 314 | message: OK 315 | - request: 316 | body: null 317 | headers: 318 | Accept: 319 | - '*/*' 320 | Accept-Encoding: 321 | - gzip, deflate 322 | Authorization: 323 | - user 324 | Connection: 325 | - keep-alive 326 | Content-Length: 327 | - '0' 328 | User-Agent: 329 | - Zammad API Python 330 | method: DELETE 331 | uri: http://localhost:8080/api/v1/tickets/5 332 | response: 333 | body: 334 | string: '' 335 | headers: 336 | Access-Control-Allow-Headers: 337 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 338 | X-File-Name, Cache-Control, Accept-Language 339 | Access-Control-Allow-Methods: 340 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 341 | Access-Control-Allow-Origin: 342 | - '*' 343 | Access-Control-Max-Age: 344 | - '1728000' 345 | Cache-Control: 346 | - no-cache 347 | Connection: 348 | - keep-alive 349 | Content-Length: 350 | - '0' 351 | Content-Security-Policy: 352 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 353 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 354 | ''self'' ''unsafe-eval'' ''nonce-UwGr3BuJKBNbBYi6hvG68w==''; style-src ''self'' 355 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 356 | Content-Type: 357 | - text/html 358 | Date: 359 | - Tue, 13 May 2025 09:11:08 GMT 360 | Referrer-Policy: 361 | - strict-origin-when-cross-origin 362 | Server: 363 | - nginx 364 | X-Content-Type-Options: 365 | - nosniff 366 | X-Download-Options: 367 | - noopen 368 | X-Frame-Options: 369 | - SAMEORIGIN 370 | X-Permitted-Cross-Domain-Policies: 371 | - none 372 | X-Request-Id: 373 | - 7a3d7c2e-b3c5-42c1-b6b1-fed453ae5ede 374 | X-Runtime: 375 | - '0.327413' 376 | X-XSS-Protection: 377 | - 1; mode=block 378 | status: 379 | code: 200 380 | message: OK 381 | - request: 382 | body: null 383 | headers: 384 | Accept: 385 | - '*/*' 386 | Accept-Encoding: 387 | - gzip, deflate 388 | Authorization: 389 | - user 390 | Connection: 391 | - keep-alive 392 | User-Agent: 393 | - Zammad API Python 394 | method: GET 395 | uri: http://localhost:8080/api/v1/tickets/search?query=Welcome&page=1&per_page=10&expand=true 396 | response: 397 | body: 398 | string: !!binary | 399 | H4sIAAAAAAAEA4VU207jMBD9leLntEpSSlCeVkLiC3aFBEKW60y71jrjyHaEAPHvjOM4kCpZ3uKZ 400 | M7czZ/L0zlTD6iJjZ2v6jo+PzipjlX8d3mXGnBcektPYs0D1JrwymGzYt0ewrGZVlecFy5hXXgO9 401 | H0BL08LGm82jaFvRXJHTvCDYFCp75wkR31QLjadA7LXO2ElZ57kF1xl0wIVfcYCTQseGVjEKeatw 402 | JUGjTic+R0htZiXje7FSdC2FL+TtuyaQuZho9M0zjcaFVFoQO5d9RqNBL6QfGGNlXh62+WFb7H/n 403 | t/W+qoubXbW/eaRNzMFnwBgS2Z85pzUFgn9MGTc8dv61EmkhzC6sV1ID969dVNUhYxcuB9h8aSIF 404 | SNMjlSe5LtLXUZDCM/eqnSQUaqSlBzvvUU0y6iycwAJKcKx+/8hY7Ljhx0n6sa8lyyoRh6oM3KZc 405 | q7jb6+uAS8Ophpp4Kp7D8ch/4IcxuJDD0GGsCCD/cKu0hT8OrKMM3y+SzPHQNvdEVjMcBUGWU1I9 406 | SjdcN8UhvBAy3T4Zyg0a2wodSoSTJdOWvpMUQoiSRsPuaEWPv96GA99RNwE0rDrw9l9YIukH2IU6 407 | 4lJZ99cgTLUmWUXtUNm78dfCPp4/AYGNcQjpBAAA 408 | headers: 409 | Access-Control-Allow-Headers: 410 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 411 | X-File-Name, Cache-Control, Accept-Language 412 | Access-Control-Allow-Methods: 413 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 414 | Access-Control-Allow-Origin: 415 | - '*' 416 | Access-Control-Max-Age: 417 | - '1728000' 418 | Cache-Control: 419 | - max-age=0, private, must-revalidate 420 | Connection: 421 | - keep-alive 422 | Content-Encoding: 423 | - gzip 424 | Content-Security-Policy: 425 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 426 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 427 | ''self'' ''unsafe-eval'' ''nonce-xFQi3QQhATHZdbOeuWmx0A==''; style-src ''self'' 428 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 429 | Content-Type: 430 | - application/json; charset=utf-8 431 | Date: 432 | - Tue, 13 May 2025 09:11:09 GMT 433 | ETag: 434 | - W/"e675291e678242b6c750376e8954e2e2" 435 | Referrer-Policy: 436 | - strict-origin-when-cross-origin 437 | Server: 438 | - nginx 439 | Transfer-Encoding: 440 | - chunked 441 | Vary: 442 | - Accept 443 | X-Content-Type-Options: 444 | - nosniff 445 | X-Download-Options: 446 | - noopen 447 | X-Frame-Options: 448 | - SAMEORIGIN 449 | X-Permitted-Cross-Domain-Policies: 450 | - none 451 | X-Request-Id: 452 | - efb4c6c0-5fdc-42b7-8973-ef9c4cce403f 453 | X-Runtime: 454 | - '0.281290' 455 | X-XSS-Protection: 456 | - 1; mode=block 457 | status: 458 | code: 200 459 | message: OK 460 | version: 1 461 | -------------------------------------------------------------------------------- /tests/cassettes/TestAPI.test_users.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Authorization: 10 | - user 11 | Connection: 12 | - keep-alive 13 | User-Agent: 14 | - Zammad API Python 15 | method: GET 16 | uri: http://localhost:8080/api/v1/users?page=1&per_page=10&expand=true 17 | response: 18 | body: 19 | string: !!binary | 20 | H4sIAAAAAAAEA+1YS2/jNhD+KwXPlqCH9aBOu23RQ1HsaXvZRSBQ0shmK5EGRSW7CfLfO5QoS87a 21 | cZLdIkkbwAdyOB4Oh/PNN9TnG8IrkvkrItWGCX7NNJciNzLRN82KNHLDBcmIQ1ak5qrTgrVg5w3b 22 | T3EVWsYbXMEhb9kGlUYLwyTvZK/KvewKilFzt5XCmDPW2Zdx0MqCN1ZYwY4p3YLQ41qnFYAdX/Pd 23 | KCy5/mpHshda2QmrKgVdN65cGuWaNR2syCUoXnPAM1oBKzW/xB3tVEhttzcnzG0IxtMcnmNYyms8 24 | uLHmYRR7ncsafzU3p7UGD6R5p/FIOcNTjCYPV0FUJ9cU7BpWggnH4op2CmpQIErAs97crki/q5iG 25 | Ki++Dlp4uaWCExLjBgm8IHK8yPHDj16ahUnmh24app/wViZb5/SUbAB3Qw8+X6yIvpIYlVJLlc/e 26 | zct3cs3+ifV6K9WcglYs8bouOVxhCinNxWY2s1Gy341Tc2zjwn37T/YWiX5s50ntzrZWPOyJY7Ph 27 | HFWMoQHIFKzCZKBDblcjuoIj6MJLmaAleImeu4VivXh3zdqWVS5GyEBiAbgPgxYKF6j72fwFRRP0 28 | Tlt6rYjUqocVedWAJJqXf4Pu8rKRnS0Tk0juAIurj7k0pw4mNMkwZebsGiQLFJ/AYuyuo/BezNLM 29 | 9zKPunHgGb0FZsOLZwYt+aXvtGxBkVOeTLj8LvguKw9i9NOAtp9+Q9rAkonMh1F5OMJPwW3CfXgE 30 | 94es+pfcikrCO/jC2h3WgFK26MES9r+jBooWoP9VAgomyB+38O/A3TLgUyh4/OuDSPg+yB8wFc28 31 | KPMSN/LXJptfPDObMoZ9Rzm2WKUUNd8gkZCWacWx97mxkB9HXGOXwsxYXomRzE3jNUZnLxKykBWy 32 | zSju+qIrFS9MlZnCaJsQLDHllgkBjTFps8caEw03TZiZ3e5L0XN7oaDlogKVY+tSbs2JTHweFxXb 33 | fi3Dsm/xHh8X6ErWjFXimX0xt9RI9AZvjYBw+g57jbsUgtXnURSyDjMvcGM/OEMhFnTBQDULCvFX 34 | wcVzkAjxseckNb5WyAVGYeoCyfsKEwjrwvuNeT2ccu2HsMqeMsifHShjcvZnvoNjTeKx8j3Rx5qe 35 | 5Q9smaUDSZ0CXVMnrYPKWdcJODRMaserWVr4ZcjCiGEclqzyETptXEXxgln+wNfOB/O8m+kFh/9V 36 | LrFsdvAufYHvuacAfQH9b3pFZM0kC303pPQc0Jd6C6D/X3rFPaqxqhyg+Bhol+UXgX5MZcJ1FD0M 37 | 19W6jgKoYqeoAJx1TD0n9WpwoPDjxKM+jf1TuPbfgP3y28EnMPgZYCO2qRvR6BywBz2aDnpvwLZf 38 | xk6g9hHAjvGNvnxgDu/2kWambz0DYScJLfHjZOzEcYyEHYS+k1JaOEEFNIEoSmhkOPgYYb8B+xW8 39 | 8344sM1Xm8RNA/8MsI1e6gZBbPTegP39wL74B2zIN/wgGQAA 40 | headers: 41 | Access-Control-Allow-Headers: 42 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 43 | X-File-Name, Cache-Control, Accept-Language 44 | Access-Control-Allow-Methods: 45 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 46 | Access-Control-Allow-Origin: 47 | - '*' 48 | Access-Control-Max-Age: 49 | - '1728000' 50 | Cache-Control: 51 | - max-age=0, private, must-revalidate 52 | Connection: 53 | - keep-alive 54 | Content-Encoding: 55 | - gzip 56 | Content-Security-Policy: 57 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 58 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 59 | ''self'' ''unsafe-eval'' ''nonce-i+1w//0bR/Cqv6sVfS68vQ==''; style-src ''self'' 60 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 61 | Content-Type: 62 | - application/json; charset=utf-8 63 | Date: 64 | - Tue, 13 May 2025 09:11:05 GMT 65 | ETag: 66 | - W/"cb2e2405ebe98f7fedf22f79c174254e" 67 | Referrer-Policy: 68 | - strict-origin-when-cross-origin 69 | Server: 70 | - nginx 71 | Transfer-Encoding: 72 | - chunked 73 | Vary: 74 | - Accept 75 | X-Content-Type-Options: 76 | - nosniff 77 | X-Download-Options: 78 | - noopen 79 | X-Frame-Options: 80 | - SAMEORIGIN 81 | X-Permitted-Cross-Domain-Policies: 82 | - none 83 | X-Request-Id: 84 | - 6e6e5204-e206-4714-b20e-ef823135cac3 85 | X-Runtime: 86 | - '0.362180' 87 | X-XSS-Protection: 88 | - 1; mode=block 89 | status: 90 | code: 200 91 | message: OK 92 | - request: 93 | body: null 94 | headers: 95 | Accept: 96 | - '*/*' 97 | Accept-Encoding: 98 | - gzip, deflate 99 | Authorization: 100 | - user 101 | Connection: 102 | - keep-alive 103 | User-Agent: 104 | - Zammad API Python 105 | method: GET 106 | uri: http://localhost:8080/api/v1/users/me 107 | response: 108 | body: 109 | string: !!binary | 110 | H4sIAAAAAAAEA8VUTW/bMAz9Lzo7ge00+/Bph5123mlDYcgy7aiQRYOWk7ZB/vtIS3GaAT30VMCA 111 | qSeK1HukeFYOe+vrTlsHraryTM1jqwO0dfNSW0Z2mUo/pF57+6qDRb9s+dm5LAZQlXrCg28RfsCz 112 | HkYHW4ODylRnaQpeD8Aev9iDIadX5CcCAzBw9ncj2EH3fDomWxb1hDOZFTtBw4c5znhAL3nY7PRz 113 | NAZsmFm0Wxg1hQF8uIabAgHwSo682jEaxoaXZOHsA6WFbluCaboePYp7p90EmToC2c6KfgnQJtgj 114 | Zw0087bHkG4g1OtFcU5Q5uV+k+83xe53/r3K91X+dbsvHv7wXe4J4hxq7PjrrLBOOe7QegpMrdYr 115 | s/td8O27ewSj0wZEljdVHQk6IPAGmPFZKDBDE2tv0He2F3jQgSwrfVaGgLsmWjawHlpsPPnYSVL/ 116 | KMYKeWywZW0jPM3NZMg2IuJVtUT1kilz0N6Dk5CpWVIw76yUXFYX9ou9+9m3IBisb4Fq1sQchJHo 117 | 8zFVUpHfypIgjx/XBSaj3VK8z76LVMkh34arpsBv5klJfZfuWWdOcUOko+9eyrfqYVfl5fZLUcpL 118 | iRWP3X3nt76ocid+hA64vbmZ/xZZ+ZipcEKeeiYg1bdeTx68/d+wk3OM6jkckG4zMMHIA+Bo4cSD 119 | iYL1/S1MTziPcXlWBQdRHQ9N9Xi5/AOXODSEegUAAA== 120 | headers: 121 | Access-Control-Allow-Headers: 122 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 123 | X-File-Name, Cache-Control, Accept-Language 124 | Access-Control-Allow-Methods: 125 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 126 | Access-Control-Allow-Origin: 127 | - '*' 128 | Access-Control-Max-Age: 129 | - '1728000' 130 | Cache-Control: 131 | - max-age=0, private, must-revalidate 132 | Connection: 133 | - keep-alive 134 | Content-Encoding: 135 | - gzip 136 | Content-Security-Policy: 137 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 138 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 139 | ''self'' ''unsafe-eval'' ''nonce-H1/vs91eZPK07t9PM3YMAg==''; style-src ''self'' 140 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 141 | Content-Type: 142 | - application/json; charset=utf-8 143 | Date: 144 | - Tue, 13 May 2025 09:11:05 GMT 145 | ETag: 146 | - W/"85cc4b4b237b0647cbbd84cd47c34845" 147 | Referrer-Policy: 148 | - strict-origin-when-cross-origin 149 | Server: 150 | - nginx 151 | Transfer-Encoding: 152 | - chunked 153 | Vary: 154 | - Accept 155 | X-Content-Type-Options: 156 | - nosniff 157 | X-Download-Options: 158 | - noopen 159 | X-Frame-Options: 160 | - SAMEORIGIN 161 | X-Permitted-Cross-Domain-Policies: 162 | - none 163 | X-Request-Id: 164 | - c70aed97-1310-46f9-b717-8a647668c10a 165 | X-Runtime: 166 | - '0.233288' 167 | X-XSS-Protection: 168 | - 1; mode=block 169 | status: 170 | code: 200 171 | message: OK 172 | - request: 173 | body: null 174 | headers: 175 | Accept: 176 | - '*/*' 177 | Accept-Encoding: 178 | - gzip, deflate 179 | Authorization: 180 | - user 181 | Connection: 182 | - keep-alive 183 | User-Agent: 184 | - Zammad API Python 185 | method: GET 186 | uri: http://localhost:8080/api/v1/users/3 187 | response: 188 | body: 189 | string: !!binary | 190 | H4sIAAAAAAAEA81UPY/bMAz9L5qdwHYu/fDUoVPnTnc4CLJMOypk0pDl5O6C/PeSseIkRW+4qQU0 191 | UE8kJT4+8ahco6pNpih0Bt2biY5QC4aT95ny1DlUlfpFO2wIvsGL6QcPa0u9ylTrwhjR9MAeP9iD 192 | IW8W5DsBA9Ab59/N4HrTcfR82XmjR5qCXbAD1BzMeYYdodzDZmteZqOn2vkENjCYEHvAeEk3xgDA 193 | Owl5c8NsWBdfk0UTxpA2pmkCjOMldC/urfEjZGoPwbUOmJIEGBvdnm+NYeJjpJheIKXrC2FlXm5X 194 | +XZVbH7mX6t8W+Wf19vi4ZHfcl/gOUC3TJJckXMnpqip5dU6oSFdeofqMXKt2iyl3p8CNu+eBRi8 195 | sSA83bR5CNBCALTAFBylJi7ZzmKwhK3rBO5NDI6pPyobwEjZYrnIBBmx6YDQ6PpViyBmdhYIqaaG 196 | yZ7hcapHjqyl5AuNqdRTpuzOIIKXlEk9KRl6JxqQ3Yn9pqH5D14RoHfYQNDMid1JRR9nJTX5lpYE 197 | IX2cFxit8efm/eu3SJc88Wu4awpwNY1q6dtZKWn6zHpakII1cFbYrGN1+5m+VA+bKi/Xn4pSPtOs 198 | gb/4LZ+u3IhfIA8seJb3U5GVz5mKB+JfZyMFfVV/8uDjP+ahxDFqprijcB2TCSaeEXsHB55dITrs 199 | rmm6QNMwb4+q4CSq5bmqnk+n3+s8+HJ6BQAA 200 | headers: 201 | Access-Control-Allow-Headers: 202 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 203 | X-File-Name, Cache-Control, Accept-Language 204 | Access-Control-Allow-Methods: 205 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 206 | Access-Control-Allow-Origin: 207 | - '*' 208 | Access-Control-Max-Age: 209 | - '1728000' 210 | Cache-Control: 211 | - max-age=0, private, must-revalidate 212 | Connection: 213 | - keep-alive 214 | Content-Encoding: 215 | - gzip 216 | Content-Security-Policy: 217 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 218 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 219 | ''self'' ''unsafe-eval'' ''nonce-oj/3QdRnqWIDpsz56qxByg==''; style-src ''self'' 220 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 221 | Content-Type: 222 | - application/json; charset=utf-8 223 | Date: 224 | - Tue, 13 May 2025 09:11:06 GMT 225 | ETag: 226 | - W/"6fa67c15cce57bb5c911126de45bde73" 227 | Referrer-Policy: 228 | - strict-origin-when-cross-origin 229 | Server: 230 | - nginx 231 | Transfer-Encoding: 232 | - chunked 233 | Vary: 234 | - Accept 235 | X-Content-Type-Options: 236 | - nosniff 237 | X-Download-Options: 238 | - noopen 239 | X-Frame-Options: 240 | - SAMEORIGIN 241 | X-Permitted-Cross-Domain-Policies: 242 | - none 243 | X-Request-Id: 244 | - 47dff695-6a11-432c-a7f1-0f04d04936fa 245 | X-Runtime: 246 | - '0.320807' 247 | X-XSS-Protection: 248 | - 1; mode=block 249 | status: 250 | code: 200 251 | message: OK 252 | - request: 253 | body: '{"firstname": "TestUser", "lastname": "LastName"}' 254 | headers: 255 | Accept: 256 | - '*/*' 257 | Accept-Encoding: 258 | - gzip, deflate 259 | Authorization: 260 | - user 261 | Connection: 262 | - keep-alive 263 | Content-Length: 264 | - '49' 265 | Content-Type: 266 | - application/json 267 | User-Agent: 268 | - Zammad API Python 269 | method: POST 270 | uri: http://localhost:8080/api/v1/users 271 | response: 272 | body: 273 | string: '{"id":67,"organization_id":null,"login":"auto-f91467a5-63df-4543-8ee8-1edb580898f9","firstname":"TestUser","lastname":"LastName","email":"","image":null,"image_source":null,"web":"","phone":"","fax":"","mobile":"","department":null,"street":"","zip":"","city":"","country":"","address":null,"vip":false,"verified":false,"active":true,"note":"","last_login":null,"source":null,"login_failed":0,"out_of_office":false,"out_of_office_start_at":null,"out_of_office_end_at":null,"out_of_office_replacement_id":null,"preferences":{"locale":"en-us"},"updated_by_id":3,"created_by_id":3,"created_at":"2025-05-13T09:11:06.248Z","updated_at":"2025-05-13T09:11:06.248Z","role_ids":[3],"two_factor_preference_ids":[],"organization_ids":[],"authorization_ids":[],"overview_sorting_ids":[],"group_ids":{}}' 274 | headers: 275 | Access-Control-Allow-Headers: 276 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 277 | X-File-Name, Cache-Control, Accept-Language 278 | Access-Control-Allow-Methods: 279 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 280 | Access-Control-Allow-Origin: 281 | - '*' 282 | Access-Control-Max-Age: 283 | - '1728000' 284 | Cache-Control: 285 | - max-age=0, private, must-revalidate 286 | Connection: 287 | - keep-alive 288 | Content-Length: 289 | - '790' 290 | Content-Security-Policy: 291 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 292 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 293 | ''self'' ''unsafe-eval'' ''nonce-5TENYkk52tsYut+Kx6BYHw==''; style-src ''self'' 294 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 295 | Content-Type: 296 | - application/json; charset=utf-8 297 | Date: 298 | - Tue, 13 May 2025 09:11:06 GMT 299 | ETag: 300 | - W/"45d34c8c9caf366955fa85fba8dd65ac" 301 | Referrer-Policy: 302 | - strict-origin-when-cross-origin 303 | Server: 304 | - nginx 305 | Vary: 306 | - Accept 307 | X-Content-Type-Options: 308 | - nosniff 309 | X-Download-Options: 310 | - noopen 311 | X-Frame-Options: 312 | - SAMEORIGIN 313 | X-Permitted-Cross-Domain-Policies: 314 | - none 315 | X-Request-Id: 316 | - e3455744-2e0f-4282-b70b-ca714a3f0fb1 317 | X-Runtime: 318 | - '0.336741' 319 | X-XSS-Protection: 320 | - 1; mode=block 321 | status: 322 | code: 201 323 | message: Created 324 | - request: 325 | body: '{"firstname": "TestUser1"}' 326 | headers: 327 | Accept: 328 | - '*/*' 329 | Accept-Encoding: 330 | - gzip, deflate 331 | Authorization: 332 | - user 333 | Connection: 334 | - keep-alive 335 | Content-Length: 336 | - '26' 337 | Content-Type: 338 | - application/json 339 | User-Agent: 340 | - Zammad API Python 341 | method: PUT 342 | uri: http://localhost:8080/api/v1/users/67 343 | response: 344 | body: 345 | string: !!binary | 346 | H4sIAAAAAAAEA3VSwW7bMAz9F53two7j1Mk3FDt1lw6DoViUK0CWDIpK1gb591GxkiwDAuhAPvJR 347 | fCRPwiix27wWwuMonfmWZLzrE+iitYWwfjRO7ISM5Eu9rdebV9mWm0bpct2um7ID6Moa1L7tqm7b 348 | 6a0ohDYYyMkJmPcOgX4GwJpxK2/wG5s/UkYhYJLGciabZpIjk5afL04ffMThhh1hv2TOn96l8kzS 349 | 8s9iTH5vbAYVzBJpAkfXcoEQgL1E+TbzYgyGvrLloyPMjlQKIYQr9ZDStbQBCnEANNoAzycDciBz 350 | 4F8JI4edp9xBEtvn6S2CHqVcQr1m7alYxQuI1HvNT5skOJd/QPtArKqXN1GPUXDqaQxhtnKANJF/ 351 | tjsjaEBwA7DYEy97kJcJgitjEOdCxFlJAtXvvy6sphADwhMktSVW1aotq7asm/dqu6vrXbV5Wa27 352 | D576tdbTvLarUx56C/wbd/Sr+V0IOnoe00Ae+3u7Oc7h/+42sRjlc/30eD/nDHte38HAkc8Kybjx 353 | XmZEH+fFPZ3PfwFUWpANFwMAAA== 354 | headers: 355 | Access-Control-Allow-Headers: 356 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 357 | X-File-Name, Cache-Control, Accept-Language 358 | Access-Control-Allow-Methods: 359 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 360 | Access-Control-Allow-Origin: 361 | - '*' 362 | Access-Control-Max-Age: 363 | - '1728000' 364 | Cache-Control: 365 | - max-age=0, private, must-revalidate 366 | Connection: 367 | - keep-alive 368 | Content-Encoding: 369 | - gzip 370 | Content-Security-Policy: 371 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 372 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 373 | ''self'' ''unsafe-eval'' ''nonce-0GTjm3ymO/WWFgQlP/Li9Q==''; style-src ''self'' 374 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 375 | Content-Type: 376 | - application/json; charset=utf-8 377 | Date: 378 | - Tue, 13 May 2025 09:11:06 GMT 379 | ETag: 380 | - W/"8b1149c82a79092c6048c7be83489006" 381 | Referrer-Policy: 382 | - strict-origin-when-cross-origin 383 | Server: 384 | - nginx 385 | Transfer-Encoding: 386 | - chunked 387 | Vary: 388 | - Accept 389 | X-Content-Type-Options: 390 | - nosniff 391 | X-Download-Options: 392 | - noopen 393 | X-Frame-Options: 394 | - SAMEORIGIN 395 | X-Permitted-Cross-Domain-Policies: 396 | - none 397 | X-Request-Id: 398 | - 79e187dc-062b-4542-9591-347f23165ea6 399 | X-Runtime: 400 | - '0.311985' 401 | X-XSS-Protection: 402 | - 1; mode=block 403 | status: 404 | code: 200 405 | message: OK 406 | - request: 407 | body: null 408 | headers: 409 | Accept: 410 | - '*/*' 411 | Accept-Encoding: 412 | - gzip, deflate 413 | Authorization: 414 | - user 415 | Connection: 416 | - keep-alive 417 | User-Agent: 418 | - Zammad API Python 419 | method: GET 420 | uri: http://localhost:8080/api/v1/users/search?query=John&page=1&per_page=10&expand=true 421 | response: 422 | body: 423 | string: !!binary | 424 | H4sIAAAAAAAEA81VTW/bMAz9Lzo7ge00+/BpBXbaebusCAxZph0NsmhIctI2yH8factOWiSHnjYg 425 | B+qJpMjHZ+bpJHQtik0i0LXS6lcZNNqSMTsYkwiDrbaiEH9wb2uEb/Asu97AWmEnEtFo54OVHZDH 426 | D/IgyMgF+Y5AAHRSm7sZdCdbip4eGw+lx8GpBTtCRcGUp9+j5XfIbOTzZHRYaRPBGnrpQgc2zOl8 427 | cAB04pBX3U+G0uElWjjY4OJB1rUD7+fQA7s30nhIxAGcbjQQJRGQKugDvRrcQNcWQ6yAWy9nwvI0 428 | 367S7Srb/Ey/Fum2SD+vt9nDb6rlbYNjQNkQSfxESpMYQokN/RrNNMRH36ClD9RrKZdW396Cre/e 429 | OeiNVMA8XY25d9CAA6uAKDhxT9SymsSg0Da6ZbiTwWmi/iSUA8lts6UDESTZxqOFuqxeShbExM4C 430 | WaywJrIn2A+Vp8iKW55pjK2eE6H20lownDKqJyazRrMG+HQmv6Gv/4MqHHTa1uBK4kTtuaOPsxKH 431 | fE1LhCx+nBfwSppxeP+6Fp6SQaqGpibArgYvlrmNSonbZ9LTgmSkgVFhk47F9cf0pXjYFGm+/pTl 432 | /DFNGrjht3x0+Yb9HBogwZO8n7Ik3yUiHJG+OhXQlRf1Rw+6frcPOY5QOYQ9usuajDDSjjhoONLu 433 | ckHb9pKmdTj00/EkMkoiGtqrYkcscEUcLx5rEhCV+Njy8rpX2vzU1Z6+VdTs9q6iCI/lkH0Svzw4 434 | Bi/1zIxXvBJXV9SOwK0/gPPuLwWmoiFABgAA 435 | headers: 436 | Access-Control-Allow-Headers: 437 | - Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, 438 | X-File-Name, Cache-Control, Accept-Language 439 | Access-Control-Allow-Methods: 440 | - POST, GET, PUT, DELETE, PATCH, OPTIONS 441 | Access-Control-Allow-Origin: 442 | - '*' 443 | Access-Control-Max-Age: 444 | - '1728000' 445 | Cache-Control: 446 | - max-age=0, private, must-revalidate 447 | Connection: 448 | - keep-alive 449 | Content-Encoding: 450 | - gzip 451 | Content-Security-Policy: 452 | - 'base-uri ''self'' http://localhost:8080; default-src ''self'' ws: wss: https://images.zammad.com; 453 | font-src ''self'' data:; img-src * data:; object-src ''none''; script-src 454 | ''self'' ''unsafe-eval'' ''nonce-EQEi+ZKpwEZ55k3iirtkag==''; style-src ''self'' 455 | ''unsafe-inline''; frame-src www.youtube.com player.vimeo.com' 456 | Content-Type: 457 | - application/json; charset=utf-8 458 | Date: 459 | - Tue, 13 May 2025 09:11:06 GMT 460 | ETag: 461 | - W/"d4cf04e1ead9205f5907888d50875110" 462 | Referrer-Policy: 463 | - strict-origin-when-cross-origin 464 | Server: 465 | - nginx 466 | Transfer-Encoding: 467 | - chunked 468 | Vary: 469 | - Accept 470 | X-Content-Type-Options: 471 | - nosniff 472 | X-Download-Options: 473 | - noopen 474 | X-Frame-Options: 475 | - SAMEORIGIN 476 | X-Permitted-Cross-Domain-Policies: 477 | - none 478 | X-Request-Id: 479 | - dda225ce-958e-4047-96c6-dd09fa51b3ae 480 | X-Runtime: 481 | - '0.285488' 482 | X-XSS-Protection: 483 | - 1; mode=block 484 | status: 485 | code: 200 486 | message: OK 487 | version: 1 488 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zammad_py import ZammadAPI 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def vcr_config(): 8 | return { 9 | "filter_headers": [("Authorization", "user")], 10 | } 11 | 12 | 13 | @pytest.fixture 14 | def zammad_api(): 15 | return ZammadAPI( 16 | url="http://localhost:8080/api/v1/", 17 | username="user", 18 | password="password", 19 | ) 20 | -------------------------------------------------------------------------------- /tests/test_zammad_py.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `zammad_py` package.""" 4 | import re 5 | 6 | import pytest 7 | 8 | # from conftest import zammad_vcr 9 | 10 | 11 | class TestAPI: 12 | @pytest.mark.vcr() 13 | def test_users(self, zammad_api): 14 | all_users = zammad_api.user.all()._items 15 | assert all_users[2]["id"] == 3 16 | assert all_users[2]["firstname"] == "John" 17 | 18 | current_user = zammad_api.user.me() 19 | assert current_user["id"] == 3 20 | assert current_user["firstname"] == "John" 21 | 22 | current_user = zammad_api.user.find(3) 23 | assert current_user["id"] == 3 24 | assert current_user["firstname"] == "John" 25 | 26 | new_user = zammad_api.user.create( 27 | {"firstname": "TestUser", "lastname": "LastName"} 28 | ) 29 | assert new_user["firstname"] == "TestUser" 30 | 31 | updated_user = zammad_api.user.update( 32 | new_user["id"], {"firstname": "TestUser1"} 33 | ) 34 | assert updated_user["firstname"] == "TestUser1" 35 | 36 | (current_user,) = zammad_api.user.search("John") 37 | assert current_user["id"] == 3 38 | 39 | @pytest.mark.vcr() 40 | def test_tickets(self, zammad_api): 41 | all_tickets = zammad_api.ticket.all()._items 42 | assert all_tickets[0]["id"] == 1 43 | assert all_tickets[0]["title"] == "Welcome to Zammad!" 44 | 45 | current_ticket = zammad_api.ticket.find(1) 46 | assert current_ticket["id"] == 1 47 | assert current_ticket["title"] == "Welcome to Zammad!" 48 | 49 | new_ticket = zammad_api.ticket.create( 50 | { 51 | "title": "some new title", 52 | "state": "new", 53 | "priority": "2 normal", 54 | "owner": "-", 55 | "customer": "nicole.braun@zammad.org", 56 | "group": "Users", 57 | "article": { 58 | "sender": "Customer", 59 | "type": "note", 60 | "subject": "some subject", 61 | "content_type": "text/plain", 62 | "body": "some body\nnext line", 63 | }, 64 | } 65 | ) 66 | assert new_ticket["title"] == "some new title" 67 | assert new_ticket["customer_id"] == 2 68 | 69 | updated_ticket = zammad_api.ticket.update( 70 | new_ticket["id"], {"title": "TestTicket1"} 71 | ) 72 | assert updated_ticket["title"] == "TestTicket1" 73 | 74 | deleted_ticket = zammad_api.ticket.destroy(new_ticket["id"]) 75 | assert deleted_ticket == b"" 76 | 77 | result = zammad_api.ticket.search("Welcome")._items 78 | assert result[0]["title"] == "Welcome to Zammad!" 79 | 80 | @pytest.mark.vcr() 81 | def test_groups(self, zammad_api): 82 | all_groups = zammad_api.group.all()._items 83 | assert all_groups[0]["id"] == 1 84 | assert all_groups[0]["note"] == "Standard Group/Pool for Tickets." 85 | 86 | current_group = zammad_api.group.find(1) 87 | assert current_group["id"] == 1 88 | assert current_group["note"] == "Standard Group/Pool for Tickets." 89 | 90 | new_group = zammad_api.group.create({"name": "Test1", "note": "note1"}) 91 | assert new_group["name"] == "Test1" 92 | assert new_group["note"] == "note1" 93 | 94 | updated_group = zammad_api.group.update(new_group["id"], {"name": "Test2"}) 95 | assert updated_group["name"] == "Test2" 96 | 97 | deleted_group = zammad_api.group.destroy(new_group["id"]) 98 | assert deleted_group == {} 99 | 100 | @pytest.mark.vcr() 101 | def test_pagination(self, zammad_api): 102 | # Let us create 5 users 103 | users = [] 104 | for index in range(5): 105 | users.append( 106 | zammad_api.user.create({"email": "pytest%s@example.com" % index}) 107 | ) 108 | paginated_response = zammad_api.user.all(filters={"per_page": 2}) 109 | 110 | # assert there are 5 users emails 111 | data = [] 112 | while True: 113 | for item in paginated_response: 114 | print(item) 115 | if re.match(r"pytest\d+@example\.com", item["email"]): 116 | data.append(item["email"]) 117 | assert item is not None 118 | print("last page?", paginated_response.is_last_page()) 119 | if paginated_response.is_last_page(): 120 | break 121 | paginated_response = paginated_response.next_page() 122 | 123 | assert len(data) == 5 124 | 125 | # Go to prev page 126 | prev_page = paginated_response.prev_page() 127 | for item in prev_page: 128 | assert item is not None 129 | for item in paginated_response: 130 | assert item is not None 131 | # Delete users 132 | for user in users: 133 | zammad_api.user.destroy(user["id"]) 134 | 135 | def test_push_on_behalf_of_header(self, zammad_api): 136 | zammad_api.on_behalf_of = "USERX" 137 | with zammad_api.request_on_behalf_of("USERXX") as api: 138 | assert api.session.headers["X-On-Behalf-Of"] == "USERXX" 139 | 140 | assert api.session.headers.get("X-On-Behalf-Of") == "USERX" 141 | 142 | def test_trailing_slash_url(self): 143 | from zammad_py import ZammadAPI 144 | 145 | url = "https://zammad.example.com" 146 | 147 | z1 = ZammadAPI(url=url + "/", username="test", password="test") 148 | assert z1.url == f"{url}/" 149 | 150 | z2 = ZammadAPI(url=url, username="test", password="test") 151 | assert z2.url == f"{url}/" 152 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = true 3 | envlist = py37, py38, py39, py310 4 | 5 | [testenv] 6 | allowlist_externals = poetry 7 | commands = 8 | poetry install -v 9 | poetry run pytest 10 | -------------------------------------------------------------------------------- /zammad_py/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for Zammad API Client.""" 2 | 3 | __author__ = """Joe Paul""" 4 | __email__ = "joeirimpan@gmail.com" 5 | __version__ = "3.2.0" 6 | 7 | from .api import ZammadAPI # noqa: F401 8 | -------------------------------------------------------------------------------- /zammad_py/api.py: -------------------------------------------------------------------------------- 1 | """Main module.""" 2 | 3 | import atexit 4 | from abc import ABC, abstractmethod 5 | from contextlib import contextmanager 6 | from typing import Any, Generator, List, Optional, Tuple 7 | 8 | import requests 9 | from requests.exceptions import HTTPError 10 | 11 | from zammad_py.exceptions import ConfigException 12 | 13 | __all__ = ["ZammadAPI"] 14 | 15 | 16 | class ZammadAPI: 17 | def __init__( 18 | self, 19 | url: str, 20 | username: Optional[str] = None, 21 | password: Optional[str] = None, 22 | http_token: Optional[str] = None, 23 | oauth2_token: Optional[str] = None, 24 | on_behalf_of: Optional[str] = None, 25 | additional_headers: Optional[List[Tuple[str, str]]] = None, 26 | ) -> None: 27 | self.url = url if url.endswith("/") else f"{url}/" 28 | self._username = username 29 | self._password = password 30 | self._http_token = http_token 31 | self._oauth2_token = oauth2_token 32 | self._on_behalf_of = on_behalf_of 33 | self._additional_headers = additional_headers 34 | self._check_config() 35 | 36 | self.session = requests.Session() 37 | atexit.register(self.session.close) 38 | self.session.headers["User-Agent"] = "Zammad API Python" 39 | if self._http_token: 40 | self.session.headers["Authorization"] = "Token token=%s" % self._http_token 41 | elif oauth2_token: 42 | self.session.headers["Authorization"] = "Bearer %s" % self._oauth2_token 43 | elif self._username and self._password: # noqa: SIM106 44 | self.session.auth = (self._username, self._password) 45 | else: 46 | raise ValueError("Invalid Authentication information in config") 47 | 48 | if self._on_behalf_of: 49 | self.session.headers["X-On-Behalf-Of"] = self._on_behalf_of 50 | 51 | if self._additional_headers: 52 | for additional_header in self._additional_headers: 53 | self.session.headers[additional_header[0]] = additional_header[1] 54 | 55 | def _check_config(self) -> None: 56 | """Check the configuration""" 57 | if not self.url: 58 | raise ConfigException("Missing url in config") 59 | if self._http_token: 60 | return 61 | if self._oauth2_token: 62 | return 63 | if not self._username: 64 | raise ConfigException("Missing username in config") 65 | if not self._password: 66 | raise ConfigException("Missing password in config") 67 | 68 | @property 69 | def on_behalf_of(self) -> Optional[str]: 70 | return self._on_behalf_of 71 | 72 | @on_behalf_of.setter 73 | def on_behalf_of(self, value: str) -> None: 74 | self._on_behalf_of = value 75 | self.session.headers["X-On-Behalf-Of"] = self._on_behalf_of 76 | 77 | @contextmanager 78 | def request_on_behalf_of( 79 | self, on_behalf_of: str 80 | ) -> Generator["ZammadAPI", None, None]: 81 | """ 82 | Use X-On-Behalf-Of Header, see https://docs.zammad.org/en/latest/api/intro.html?highlight=on%20behalf#actions-on-behalf-of-other-users 83 | 84 | :param on_behalf_of: The value of this header can be one of the following: user ID, login or email 85 | 86 | """ 87 | initial_value = self.session.headers["X-On-Behalf-Of"] 88 | self.session.headers["X-On-Behalf-Of"] = on_behalf_of 89 | yield self 90 | self.session.headers["X-On-Behalf-Of"] = initial_value 91 | 92 | @property 93 | def group(self) -> "Group": 94 | """Return a `Group` instance""" 95 | return Group(connection=self) 96 | 97 | @property 98 | def organization(self) -> "Organization": 99 | """Return a `Organization` instance""" 100 | return Organization(connection=self) 101 | 102 | @property 103 | def role(self) -> "Role": 104 | """Return a `Role` instance""" 105 | return Role(connection=self) 106 | 107 | @property 108 | def ticket(self) -> "Ticket": 109 | """Return a `Ticket` instance""" 110 | return Ticket(connection=self) 111 | 112 | @property 113 | def link(self): 114 | """Return a `Link` instance""" 115 | return Link(connection=self) 116 | 117 | @property 118 | def ticket_article(self) -> "TicketArticle": 119 | """Return a `TicketArticle` instance""" 120 | return TicketArticle(connection=self) 121 | 122 | @property 123 | def ticket_article_attachment(self) -> "TicketArticleAttachment": 124 | """Return a `TicketArticleAttachment` instance""" 125 | return TicketArticleAttachment(connection=self) 126 | 127 | @property 128 | def ticket_article_plain(self) -> "TicketArticlePlain": 129 | """Return a `TicketArticlePlain` instance""" 130 | return TicketArticlePlain(connection=self) 131 | 132 | @property 133 | def ticket_priority(self) -> "TicketPriority": 134 | """Return a `TicketPriority` instance""" 135 | return TicketPriority(connection=self) 136 | 137 | @property 138 | def ticket_state(self) -> "TicketState": 139 | """Return a `TicketState` instance""" 140 | return TicketState(connection=self) 141 | 142 | @property 143 | def user(self) -> "User": 144 | """Return a `User` instance""" 145 | return User(connection=self) 146 | 147 | @property 148 | def taglist(self) -> "TagList": 149 | """Retrun a TagList instance""" 150 | return TagList(connection=self) 151 | 152 | @property 153 | def ticket_tag(self): 154 | """Return a `TicketTag` instance""" 155 | return TicketTag(connection=self) 156 | 157 | 158 | class Pagination: 159 | def __init__( 160 | self, 161 | items, 162 | resource: "Resource", 163 | function_name: str, 164 | params=None, 165 | page: int = 1, 166 | ) -> None: 167 | self._items = items 168 | self._page = page 169 | self._resource = resource 170 | # Create a copy of params and remove page to prevent it from overriding the incremented page value 171 | self._params = params.copy() if params else {} 172 | if ( 173 | self._params 174 | and "filters" in self._params 175 | and isinstance(self._params["filters"], dict) 176 | and "page" in self._params["filters"] 177 | ): 178 | self._params["filters"] = self._params["filters"].copy() 179 | self._params["filters"].pop("page", None) 180 | self._function_name = function_name 181 | 182 | def is_last_page(self) -> bool: 183 | """Check if the current page is the last page""" 184 | if len(self._items) < self._resource.per_page: 185 | return True 186 | return False 187 | 188 | def __iter__(self): 189 | yield from self._items 190 | 191 | def __len__(self) -> int: 192 | return len(self._items) 193 | 194 | def __getitem__(self, index: int): 195 | return self._items[index] 196 | 197 | def __setitem__(self, index: int, value) -> None: 198 | self._items[index] = value 199 | 200 | def next_page(self) -> "Pagination": 201 | self._page += 1 202 | return getattr(self._resource, self._function_name)( 203 | page=self._page, **self._params 204 | ) 205 | 206 | def prev_page(self) -> "Pagination": 207 | self._page -= 1 208 | return getattr(self._resource, self._function_name)( 209 | page=self._page, **self._params 210 | ) 211 | 212 | 213 | class Resource(ABC): 214 | def __init__(self, connection: ZammadAPI, per_page: int = 10) -> None: 215 | self._connection = connection 216 | self._per_page = per_page 217 | 218 | @property 219 | @abstractmethod 220 | def path_attribute(self) -> str: 221 | ... 222 | 223 | @property 224 | def url(self) -> str: 225 | """Returns a the full url concatenated with the resource class name""" 226 | return self._connection.url + self.path_attribute 227 | 228 | @property 229 | def per_page(self) -> int: 230 | return self._per_page 231 | 232 | @per_page.setter 233 | def per_page(self, value: int) -> None: 234 | self._per_page = value 235 | 236 | def _raise_or_return_json(self, response: requests.Response) -> Any: 237 | """Raise HTTPError before converting response to json 238 | 239 | :param response: Request response object 240 | """ 241 | try: 242 | response.raise_for_status() 243 | except HTTPError: 244 | raise HTTPError(response.text) 245 | 246 | try: 247 | json_value = response.json() 248 | except ValueError: 249 | return response.content 250 | else: 251 | return json_value 252 | 253 | def all(self, page: int = 1, filters=None) -> Pagination: 254 | """Returns the list of resources 255 | 256 | :param page: Page number 257 | :param filters: Filter arguments including page, per_page if needed 258 | """ 259 | params = filters.copy() if filters else {} 260 | 261 | # Set defaults only if not specified in filters 262 | if "page" not in params: 263 | params["page"] = page 264 | if "per_page" not in params: 265 | params["per_page"] = self._per_page 266 | if "expand" not in params: 267 | params["expand"] = "true" 268 | 269 | if "per_page" in params: 270 | self._per_page = params["per_page"] 271 | 272 | response = self._connection.session.get(self.url, params=params) 273 | data = self._raise_or_return_json(response) 274 | 275 | return Pagination( 276 | items=data, 277 | resource=self, 278 | function_name="all", 279 | params={"filters": params}, 280 | page=params["page"], 281 | ) 282 | 283 | def search(self, search_string: str, page: int = 1, filters=None) -> Pagination: 284 | """Returns the list of resources 285 | 286 | :param search_string: option to filter for 287 | :param page: Page number 288 | :param filters: Filter arguments like page, per_page 289 | """ 290 | params = filters.copy() if filters else {} 291 | params.update({"query": search_string}) 292 | 293 | # Set defaults only if not specified in filters 294 | if "page" not in params: 295 | params["page"] = page 296 | if "per_page" not in params: 297 | params["per_page"] = self._per_page 298 | if "expand" not in params: 299 | params["expand"] = "true" 300 | 301 | if "per_page" in params: 302 | self._per_page = params["per_page"] 303 | 304 | response = self._connection.session.get(self.url + "/search", params=params) 305 | data = self._raise_or_return_json(response) 306 | 307 | return Pagination( 308 | items=data, 309 | resource=self, 310 | function_name="search", 311 | params={"search_string": search_string, "filters": params}, 312 | page=params["page"], 313 | ) 314 | 315 | def find(self, id): 316 | """Return the resource associated with the id 317 | 318 | :param id: Resource id 319 | """ 320 | response = self._connection.session.get(self.url + "/%s" % id) 321 | return self._raise_or_return_json(response) 322 | 323 | def create(self, params): 324 | """Create the requested resource 325 | 326 | :param params: Resource data for creating 327 | """ 328 | response = self._connection.session.post(self.url, json=params) 329 | return self._raise_or_return_json(response) 330 | 331 | def update(self, id, params): 332 | """Update the requested resource 333 | 334 | :param id: Resource id 335 | :param params: Resource data for updating 336 | """ 337 | response = self._connection.session.put(self.url + "/%s" % id, json=params) 338 | return self._raise_or_return_json(response) 339 | 340 | def destroy(self, id): 341 | """Delete the resource associated with the id 342 | 343 | :param id: Resource id 344 | """ 345 | response = self._connection.session.delete(self.url + "/%s" % id) 346 | return self._raise_or_return_json(response) 347 | 348 | 349 | class Group(Resource): 350 | path_attribute = "groups" 351 | 352 | 353 | class Role(Resource): 354 | path_attribute = "roles" 355 | 356 | 357 | class Organization(Resource): 358 | path_attribute = "organizations" 359 | 360 | 361 | class Ticket(Resource): 362 | path_attribute = "tickets" 363 | 364 | def articles(self, id): 365 | """Returns all the articles associated with the ticket id 366 | 367 | :param id: Ticket id 368 | """ 369 | response = self._connection.session.get( 370 | self._connection.url + "ticket_articles/by_ticket/%s?expand=true" % id 371 | ) 372 | return self._raise_or_return_json(response) 373 | 374 | def tags(self, id): 375 | """Returns all the tags associated with the ticket id 376 | 377 | :param id: Ticket id 378 | """ 379 | response = self._connection.session.get( 380 | self._connection.url + f"tags?object=Ticket&o_id={id}" 381 | ) 382 | return self._raise_or_return_json(response) 383 | 384 | def merge(self, id, number): 385 | """Merges two tickets, (undocumented in Zammad Docs) 386 | If the objects are already merged, it will return "Object already exists!" 387 | Attention: Must use password to authenticate to Zammad, otherwise this will not work! 388 | :param id: Ticket id of the child 389 | :param number: Ticket Number of the Parent 390 | """ 391 | 392 | response = self._connection.session.put( 393 | self._connection.url + f"ticket_merge/{id}/{number}" 394 | ) 395 | return self._raise_or_return_json(response) 396 | 397 | 398 | class Link(Resource): 399 | path_attribute = "links" 400 | 401 | def add( 402 | self, 403 | link_object_target_value, 404 | link_object_source_number, 405 | link_type="normal", 406 | link_object_target="Ticket", 407 | link_object_source="Ticket", 408 | ): 409 | """Create the link 410 | 411 | :params link_type: Link type ('normal', 'parent', 'child') 412 | :params link_object_target: (for now*: 'Ticket') 413 | :params link_object_target_value: Ticket ID 414 | :params link_object_source: (for now*: 'Ticket') 415 | :params link_object_source_number: Ticket Number (Not the ID!) 416 | 417 | *Currently, only Tickets can be linked together. 418 | """ 419 | params = { 420 | "link_type": link_type, 421 | "link_object_target": link_object_target, 422 | "link_object_target_value": link_object_target_value, 423 | "link_object_source": link_object_source, 424 | "link_object_source_number": link_object_source_number, 425 | } 426 | 427 | response = self._connection.session.post(self.url + "/add", json=params) 428 | return self._raise_or_return_json(response) 429 | 430 | def remove( 431 | self, 432 | link_object_target_value, 433 | link_object_source_number, 434 | link_type="normal", 435 | link_object_target="Ticket", 436 | link_object_source="Ticket", 437 | ): 438 | """Remove the Link 439 | 440 | :params link_type: Link type ('normal', 'parent', 'child') 441 | :params link_object_target: (for now: 'Ticket') 442 | :params link_object_target_value: Ticket ID 443 | :params link_object_source: (for now: 'Ticket') 444 | :params link_object_source_number: Ticket ID 445 | """ 446 | params = { 447 | "link_type": link_type, 448 | "link_object_target": link_object_target, 449 | "link_object_target_value": link_object_target_value, 450 | "link_object_source": link_object_source, 451 | "link_object_source_number": link_object_source_number, 452 | } 453 | 454 | response = self._connection.session.delete(self.url + "/remove", json=params) 455 | return self._raise_or_return_json(response) 456 | 457 | def get(self, id): 458 | """Returns all the links associated with the ticket id 459 | 460 | :param id: Ticket id 461 | """ 462 | params = {"link_object": "Ticket", "link_object_value": id} 463 | response = self._connection.session.get( 464 | self._connection.url + self.path_attribute, params=params 465 | ) 466 | return self._raise_or_return_json(response) 467 | 468 | 469 | class TicketArticle(Resource): 470 | path_attribute = "ticket_articles" 471 | 472 | 473 | class TicketArticleAttachment(Resource): 474 | path_attribute = "ticket_attachment" 475 | 476 | def download(self, id, article_id, ticket_id): 477 | """Download the ticket attachment associated with the ticket id 478 | 479 | :param id: Ticket attachment id 480 | :param article_id: Ticket article id 481 | :param ticket_id: Ticket id 482 | """ 483 | response = self._connection.session.get( 484 | self.url + f"/{ticket_id}/{article_id}/{id}" 485 | ) 486 | return self._raise_or_return_json(response) 487 | 488 | 489 | class TicketArticlePlain(Resource): 490 | path_attribute = "ticket_article_plain" 491 | 492 | 493 | class TicketPriority(Resource): 494 | path_attribute = "ticket_priorities" 495 | 496 | 497 | class TicketState(Resource): 498 | path_attribute = "ticket_states" 499 | 500 | 501 | class User(Resource): 502 | path_attribute = "users" 503 | 504 | def me(self): 505 | """Returns current user information""" 506 | response = self._connection.session.get(self.url + "/me") 507 | return self._raise_or_return_json(response) 508 | 509 | 510 | class OnlineNotification(Resource): 511 | path_attribute = "online_notifications" 512 | 513 | def mark_all_read(self): 514 | """Marks all online notification as read""" 515 | response = self._connection.session.post(self.url + "/mark_all_as_read") 516 | return self._raise_or_return_json(response) 517 | 518 | 519 | class Object(Resource): 520 | path_attribute = "object_manager_attributes" 521 | 522 | def execute_migrations(self): 523 | """Executes all database migrations""" 524 | response = self._connection.session.post( 525 | self._connection.url + "object_manager_attributes_execute_migrations" 526 | ) 527 | return self._raise_or_return_json(response) 528 | 529 | 530 | class TagList(Resource): 531 | """TagList handles tags in admin scope""" 532 | 533 | path_attribute = "tag_list" 534 | 535 | 536 | class TicketTag(Resource): 537 | """handles tags in the ticket scope""" 538 | 539 | path_attribute = "tags" 540 | 541 | def add(self, id, tag, object="Ticket"): 542 | """Add a tag to a ticket 543 | 544 | :param id: Ticket id 545 | :param tag: Tag name 546 | :param object: Object to tag ((for now: 'Ticket')) 547 | """ 548 | 549 | params = { 550 | "o_id": id, 551 | "item": tag, 552 | "object": object, 553 | } 554 | 555 | response = self._connection.session.post(self.url + "/add", json=params) 556 | return self._raise_or_return_json(response) 557 | 558 | def remove(self, id, tag, object="Ticket"): 559 | """Remove a tag from a ticket. 560 | 561 | :param id: Ticket id 562 | :param tag: Tag name 563 | :param object: Object to tag ((for now: 'Ticket')) 564 | """ 565 | 566 | params = { 567 | "o_id": id, 568 | "item": tag, 569 | "object": object, 570 | } 571 | 572 | response = self._connection.session.delete(self.url + "/remove", json=params) 573 | return self._raise_or_return_json(response) 574 | -------------------------------------------------------------------------------- /zammad_py/exceptions.py: -------------------------------------------------------------------------------- 1 | class ConfigException(Exception): 2 | pass 3 | --------------------------------------------------------------------------------