├── tests
├── __init__.py
├── hello.pdf
├── test_with_cups.py
├── test_widget.py
├── test_async_subprocess.py
├── test_highlevel.py
└── test_form.py
├── pyipptool
├── templates
│ └── ipp
│ │ ├── item.pt
│ │ ├── constant_tuple.pt
│ │ ├── form.pt
│ │ └── group_tuple.pt
├── __init__.py
├── config.py
├── widgets.py
├── forms.py
├── schemas.py
└── core.py
├── .gitignore
├── MANIFEST.in
├── tox.ini
├── LICENSE.txt
├── .travis.yml
├── setup.py
└── README.rst
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pyipptool/templates/ipp/item.pt:
--------------------------------------------------------------------------------
1 | ${field.serialize(cstruct)}
2 |
--------------------------------------------------------------------------------
/tests/hello.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ezeep/pyipptool/HEAD/tests/hello.pdf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | *.egg/
4 | *.egg-info/
5 | *.pyc
6 | .coverage
7 | .cache/
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include LICENSE.txt
3 | include pyipptool/templates/ipp/*
4 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py27,py34
3 | [testenv]
4 | deps=pytest
5 | pytest-pep8
6 | future
7 | futures
8 | tornado
9 | pkipplib
10 | mock
11 | commands=py.test --pep8 pyipptool tests
12 |
--------------------------------------------------------------------------------
/pyipptool/templates/ipp/constant_tuple.pt:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/pyipptool/templates/ipp/form.pt:
--------------------------------------------------------------------------------
1 |
2 | {
3 |
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/pyipptool/templates/ipp/group_tuple.pt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2013 ezeep GmbH
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.4"
5 |
6 |
7 | before_install:
8 | - sudo apt-get update
9 | - sudo apt-get install -qq cups cups-pdf
10 | - curl http://www.cups.org/software/ipptool/ipptool-20130731-linux-ubuntu-x86_64.tar.gz | tar xvzf -
11 | - "echo \"[main]\nipptool_path = \"$TRAVIS_BUILD_DIR\"/ipptool-20130731/ipptool\ncups_uri = http://localhost:631/\" > ~/.pyipptool.cfg"
12 | - cat ~/.pyipptool.cfg
13 | - echo "$USER:travis" | sudo chpasswd
14 | - sudo pip install -U pip setuptools
15 |
16 | install:
17 | - pip install -e . pytest-cov coveralls pytest mock tornado pkipplib pytest-pep8
18 |
19 | script:
20 | - py.test --cov pyipptool --ignore ipptool-20130731 --pep8
21 |
22 | after_success:
23 | coveralls
24 |
25 | notifications:
26 | email: false
27 |
--------------------------------------------------------------------------------
/pyipptool/__init__.py:
--------------------------------------------------------------------------------
1 | from .config import get_config
2 | from .core import IPPToolWrapper
3 |
4 |
5 | config = get_config()
6 |
7 | wrapper = IPPToolWrapper(config)
8 | create_job_subscription = wrapper.create_job_subscription
9 | create_printer_subscription = wrapper. create_printer_subscription
10 | cancel_job = wrapper.cancel_job
11 | release_job = wrapper.release_job
12 | create_job = wrapper.create_job
13 | create_printer_subscription = wrapper.create_printer_subscription
14 | cups_add_modify_class = wrapper.cups_add_modify_class
15 | cups_add_modify_printer = wrapper.cups_add_modify_printer
16 | cups_delete_printer = wrapper.cups_delete_printer
17 | cups_delete_class = wrapper.cups_delete_class
18 | cups_get_classes = wrapper.cups_get_classes
19 | cups_get_devices = wrapper.cups_get_devices
20 | cups_get_ppd = wrapper.cups_get_ppd
21 | cups_get_ppds = wrapper.cups_get_ppds
22 | cups_get_printers = wrapper.cups_get_printers
23 | cups_move_job = wrapper.cups_move_job
24 | cups_reject_jobs = wrapper.cups_reject_jobs
25 | get_job_attributes = wrapper.get_job_attributes
26 | get_jobs = wrapper.get_jobs
27 | get_printer_attributes = wrapper.get_printer_attributes
28 | get_subscriptions = wrapper.get_subscriptions
29 | get_notifications = wrapper.get_notifications
30 | pause_printer = wrapper.pause_printer
31 | print_job = wrapper.print_job
32 | resume_printer = wrapper.resume_printer
33 | send_document = wrapper.send_document
34 | hold_new_jobs = wrapper.hold_new_jobs
35 | release_held_new_jobs = wrapper.release_held_new_jobs
36 | cancel_subscription = wrapper.cancel_subscription
37 |
--------------------------------------------------------------------------------
/pyipptool/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from future import standard_library
4 | with standard_library.hooks():
5 | import configparser
6 |
7 |
8 | def read_config(paths=()):
9 | config = {}
10 | fs_config = configparser.ConfigParser()
11 | fs_config.read(paths)
12 | config['cups_uri'] = fs_config.get('main', 'cups_uri')
13 | config['ipptool_path'] = fs_config.get('main', 'ipptool_path')
14 | try:
15 | config['login'] = fs_config.get('main', 'login')
16 | except configparser.NoOptionError:
17 | pass
18 | try:
19 | config['password'] = fs_config.get('main', 'password')
20 | except configparser.NoOptionError:
21 | pass
22 | try:
23 | config['graceful_shutdown_time'] = fs_config.getint(
24 | 'main',
25 | 'graceful_shutdown_time')
26 | except configparser.NoOptionError:
27 | config['graceful_shutdown_time'] = 2
28 | try:
29 | config['timeout'] = fs_config.getint('main', 'timeout')
30 | except configparser.NoOptionError:
31 | config['timeout'] = 10
32 | return config
33 |
34 |
35 | class LazyConfig(dict):
36 | def __init__(self, paths):
37 | self.paths = paths
38 | self.loaded = False
39 |
40 | def __getitem__(self, key):
41 | if not self.loaded:
42 | self.update(read_config(self.paths))
43 | self.loaded = True
44 | return super(LazyConfig, self).__getitem__(key)
45 |
46 |
47 | def get_config(paths=('/etc/opt/pyipptool/pyipptool.cfg',
48 | os.path.join(os.path.expanduser('~'),
49 | '.pyipptool.cfg'))):
50 | return LazyConfig(paths)
51 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from setuptools import setup
4 | from setuptools.command.test import test as TestCommand
5 |
6 |
7 | class PyTest(TestCommand):
8 | def finalize_options(self):
9 | TestCommand.finalize_options(self)
10 | self.test_args = []
11 | self.test_suite = True
12 |
13 | def run_tests(self):
14 | import pytest
15 | errno = pytest.main(self.test_args)
16 | sys.exit(errno)
17 |
18 |
19 | version = '0.5.0dev'
20 |
21 |
22 | def read_that_file(path):
23 | with open(path) as open_file:
24 | return open_file.read()
25 |
26 |
27 | description = '\n'.join((read_that_file('README.rst'),
28 | read_that_file('LICENSE.txt')))
29 |
30 | setup(
31 | name='pyipptool',
32 | version=version,
33 | author='Nicolas Delaby',
34 | author_email='nicolas.delaby@ezeep.com',
35 | description='ipptool python wrapper',
36 | url='https://github.com/ezeep/pyipptool',
37 | long_description=description,
38 | license='Apache Software License',
39 | packages=('pyipptool',),
40 | install_requires=('deform>=2.0a2', 'future',),
41 | extra_requires={'Tornado': ('tornado', 'futures')},
42 | tests_require=('mock', 'pytest', 'coverage', 'pytest-pep8',
43 | 'pytest-cov', 'coveralls', 'pkipplib', 'tornado',
44 | 'tox',),
45 | include_package_data=True,
46 | test_suite='tests',
47 | cmdclass = {'test': PyTest},
48 | classifiers = [
49 | 'Development Status :: 4 - Beta',
50 | 'Intended Audience :: Developers',
51 | 'Intended Audience :: System Administrators',
52 | 'License :: OSI Approved :: Apache Software License',
53 | 'Operating System :: OS Independent',
54 | 'Programming Language :: Python :: 2.7',
55 | 'Programming Language :: Python :: 3.4',
56 | 'Topic :: Printing',
57 | ],
58 | keywords='cups ipptool ipp printing tornado',
59 | )
60 |
--------------------------------------------------------------------------------
/pyipptool/widgets.py:
--------------------------------------------------------------------------------
1 | import colander
2 | from deform.widget import MappingWidget, SequenceWidget, Widget
3 | from future.builtins import bytes, str
4 |
5 |
6 | class IPPDisplayWidget(Widget):
7 | def serialize(self, field, cstruct=None, readonly=False):
8 | return 'DISPLAY {}'.format(field.name.replace('_', '-'))
9 |
10 |
11 | class IPPNameWidget(Widget):
12 | def serialize(self, field, cstruct=None, readonly=False):
13 | name = field.name
14 | while field.parent is not None:
15 | field = field.parent
16 | value = getattr(field.schema, name)
17 | return '{} "{}"'.format(name.upper(), value)
18 |
19 |
20 | class IPPFileWidget(Widget):
21 | def serialize(self, field, cstruct=None, readonly=False):
22 | if cstruct is colander.null:
23 | return ''
24 | if not isinstance(cstruct, (str, bytes)):
25 | raise ValueError('Wrong value provided for field {!r}'.format(
26 | field.name))
27 | return 'FILE {}'.format(cstruct)
28 |
29 |
30 | class IPPAttributeWidget(Widget):
31 | def serialize(self, field, cstruct=None, readonly=False):
32 | if cstruct is colander.null:
33 | return ''
34 | if cstruct is None:
35 | raise ValueError('None value provided for {!r}'.format(field.name))
36 | attr_name = field.schema.typ.__class__.__name__
37 | attr_name = attr_name[0].lower() + attr_name[1:]
38 | return 'ATTR {attr_type} {attr_name} {attr_value}'.format(
39 | attr_type=attr_name,
40 | attr_name=field.name.replace('_', '-'),
41 | attr_value=cstruct)
42 |
43 |
44 | class IPPBodyWidget(MappingWidget):
45 | readonly_template = 'ipp/form'
46 | template = readonly_template
47 | item_template = 'ipp/item'
48 |
49 |
50 | class IPPGroupWidget(SequenceWidget):
51 | readonly_template = 'ipp/group_tuple'
52 | template = readonly_template
53 | item_template = 'ipp/item'
54 |
55 |
56 | class IPPConstantTupleWidget(SequenceWidget):
57 | readonly_template = 'ipp/constant_tuple'
58 | template = readonly_template
59 | item_template = 'ipp/item'
60 |
--------------------------------------------------------------------------------
/tests/test_with_cups.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pytest
4 |
5 | TRAVIS = os.getenv('TRAVIS')
6 | TRAVIS_USER = os.getenv('USER')
7 | TRAVIS_BUILD_DIR = os.getenv('TRAVIS_BUILD_DIR')
8 |
9 |
10 | @pytest.mark.skipif(TRAVIS != 'true', reason='requires travis')
11 | class TestWithCups(object):
12 |
13 | ipptool_path = '%s/ipptool-20130731/ipptool' % TRAVIS_BUILD_DIR
14 | config = {'ipptool_path': ipptool_path,
15 | 'cups_uri': 'http://localhost:631/',
16 | 'login': TRAVIS_USER,
17 | 'password': 'travis',
18 | 'graceful_shutdown_time': 2,
19 | 'timeout': 5}
20 |
21 | def test_cups_get_printers(self):
22 | import pyipptool
23 | ipptool = pyipptool.core.IPPToolWrapper(self.config)
24 | response = ipptool.cups_get_printers()
25 | assert response['Name'] == 'CUPS Get Printers'
26 | assert response['Operation'] == 'CUPS-Get-Printers'
27 | assert response['RequestAttributes'] == [{
28 | 'attributes-charset': 'utf-8',
29 | 'attributes-natural-language': 'en'}]
30 | assert len(response['ResponseAttributes']) == 2
31 | assert response['ResponseAttributes'][1]['printer-name'] == 'PDF'
32 | assert response['StatusCode'] == 'successful-ok'
33 | assert response['Successful']
34 |
35 | def test_cups_get_ppds(self):
36 | import pyipptool
37 | ipptool = pyipptool.core.IPPToolWrapper(self.config)
38 | response = ipptool.cups_get_ppds(ppd_make_and_model='generic pdf')
39 | assert response['Name'] == 'CUPS Get PPDs'
40 | assert response['Operation'] == 'CUPS-Get-PPDs'
41 | assert response['RequestAttributes'] == [{
42 | 'attributes-charset': 'utf-8',
43 | 'attributes-natural-language': 'en',
44 | 'ppd-make-and-model': 'generic pdf'}]
45 | assert len(response['ResponseAttributes']) == 2
46 | assert 'ppd-make-and-model' in response['ResponseAttributes'][1]
47 | ppd = response['ResponseAttributes'][1]
48 | assert ppd['ppd-make-and-model'] == 'Generic PDF Printer'
49 | assert response['StatusCode'] == 'successful-ok'
50 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | pyipptool
2 | =========
3 |
4 | .. image::
5 | https://travis-ci.org/ezeep/pyipptool.svg?branch=master
6 | :target: https://travis-ci.org/ezeep/pyipptool
7 |
8 | .. image:: https://coveralls.io/repos/ezeep/pyipptool/badge.png
9 | :target: https://coveralls.io/r/ezeep/pyipptool
10 |
11 | .. image:: https://pypip.in/v/pyipptool/badge.png
12 | :target: https://crate.io/packages/pyipptool/
13 | :alt: Latest PyPI version
14 |
15 | .. image:: https://landscape.io/github/ezeep/pyipptool/master/landscape.png
16 | :target: https://landscape.io/github/ezeep/pyipptool/master
17 | :alt: Code Health
18 |
19 |
20 | Convenient IPP request generator for python to interrogate CUPS or IPP devices, with the help of ipptool_.
21 |
22 | .. _ipptool: http://www.cups.org/documentation.php/doc-1.7/man-ipptool.html
23 |
24 | Setup
25 | -----
26 |
27 | .. code-block:: console
28 |
29 | python setup.py install
30 |
31 |
32 | Tests
33 | -----
34 |
35 | .. code-block:: console
36 |
37 | python setup.py test
38 |
39 | Configuration
40 | -------------
41 |
42 | Add the following content in ``~/.pyipptool.cfg`` or ``/etc/pyipptool/pyipptol.cfg``.
43 |
44 | .. code-block:: ini
45 |
46 | [main]
47 | ipptool_path = /usr/bin/ipptool
48 | cups_uri = http://localhost:631/
49 | ;If authentication is required
50 | login = admin
51 | password = secret
52 | graceful_shutdown_time = 2
53 | timeout = 10
54 |
55 |
56 | Where ``ipptool_path`` points to the absolute path of your installed ipptool
57 |
58 | Usage
59 | -----
60 |
61 | Create an infinite time subscription for printer-XYZ class for the ``rss`` notifier
62 |
63 | .. code-block:: python
64 |
65 | >>> from pyipptool import create_printer_subscription
66 | >>> create_printer_subscription(
67 | printer_uri='http://localhost:631/classes/printer-XYZ',
68 | requesting_user_name='admin',
69 | notify_recipient_uri='rss://',
70 | notify_events='all',
71 | notify_lease_duration=0)
72 | {'Name': 'Create Printer Subscription',
73 | 'Operation': 'Create-Printer-Subscription',
74 | 'RequestAttributes': [{'attributes-charset': 'utf-8',
75 | 'attributes-natural-language': 'en',
76 | 'printer-uri': 'http://localhost:631/classes/printer-XYZ',
77 | 'requesting-user-name': 'admin'},
78 | {'notify-events': 'all',
79 | 'notify-lease-duration': 0,
80 | 'notify-recipient-uri': 'rss://'}],
81 | 'ResponseAttributes': [{'attributes-charset': 'utf-8',
82 | 'attributes-natural-language': 'en'},
83 | {'notify-subscription-id': 23}],
84 | 'StatusCode': 'successful-ok',
85 | 'Successful': True,
86 | 'notify-subscription-id': 23}
87 |
--------------------------------------------------------------------------------
/tests/test_widget.py:
--------------------------------------------------------------------------------
1 | import colander
2 | from deform.tests.test_widget import DummyField, DummyRenderer
3 | import pytest
4 |
5 |
6 | class DummyType(object):
7 | pass
8 |
9 |
10 | class DummySchema(object):
11 | def __init__(self, **kw):
12 | self.__dict__.update(kw)
13 |
14 |
15 | def test_ipp_attribute_widget_null():
16 | from pyipptool.widgets import IPPAttributeWidget
17 | widget = IPPAttributeWidget()
18 | rendrer = DummyRenderer()
19 | field = DummyField(None, renderer=rendrer)
20 | response = widget.serialize(field, colander.null)
21 | assert response == ''
22 |
23 |
24 | def test_ipp_attribute_widget_with_value():
25 | from pyipptool.widgets import IPPAttributeWidget
26 | widget = IPPAttributeWidget()
27 | rendrer = DummyRenderer()
28 | schema = DummySchema()
29 | schema.typ = DummyType()
30 | field = DummyField(schema, renderer=rendrer)
31 | response = widget.serialize(field, 'hello')
32 | assert response == 'ATTR dummyType name hello'
33 |
34 |
35 | def test_ipp_attribute_widget_with_None():
36 | from pyipptool.widgets import IPPAttributeWidget
37 | widget = IPPAttributeWidget()
38 | rendrer = DummyRenderer()
39 | schema = DummySchema()
40 | schema.typ = DummyType()
41 | field = DummyField(schema, renderer=rendrer)
42 | with pytest.raises(ValueError) as exc_info:
43 | widget.serialize(field, None)
44 | assert str(exc_info.value) == "None value provided for 'name'"
45 |
46 |
47 | def test_ipp_display_widget():
48 | from pyipptool.widgets import IPPDisplayWidget
49 | widget = IPPDisplayWidget()
50 | rendrer = DummyRenderer()
51 | field = DummyField(None, renderer=rendrer)
52 | response = widget.serialize(field, None)
53 | assert response == 'DISPLAY name'
54 |
55 |
56 | def test_ipp_name_widget():
57 | from pyipptool.widgets import IPPNameWidget
58 | widget = IPPNameWidget()
59 | rendrer = DummyRenderer()
60 | schema = DummySchema(name='FOO')
61 |
62 | field = DummyField(schema, renderer=rendrer)
63 | field.parent = None
64 | sub_field = DummyField(None, renderer=rendrer)
65 | sub_field.parent = field
66 |
67 | response = widget.serialize(sub_field, 'world')
68 | assert response == 'NAME "FOO"'
69 |
70 |
71 | def test_ipp_file_widget():
72 | from pyipptool.widgets import IPPFileWidget
73 |
74 | widget = IPPFileWidget()
75 | rendrer = DummyRenderer()
76 |
77 | field = DummyField(None, renderer=rendrer)
78 | response = widget.serialize(field, '/path/to/file.txt')
79 | assert response == 'FILE /path/to/file.txt'
80 |
81 |
82 | def test_ipp_file_widget_with_None():
83 | from pyipptool.widgets import IPPFileWidget
84 |
85 | widget = IPPFileWidget()
86 | rendrer = DummyRenderer()
87 |
88 | field = DummyField(None, renderer=rendrer)
89 | with pytest.raises(ValueError):
90 | widget.serialize(field, None)
91 |
--------------------------------------------------------------------------------
/pyipptool/forms.py:
--------------------------------------------------------------------------------
1 | from pkg_resources import resource_filename
2 |
3 | import deform.template
4 |
5 | from .schemas import (cancel_job_schema,
6 | release_job_schema,
7 | create_job_schema,
8 | create_job_subscription_schema,
9 | create_printer_subscription_schema,
10 | cups_add_modify_class_schema,
11 | cups_add_modify_printer_schema,
12 | cups_delete_printer_schema,
13 | cups_delete_class_schema,
14 | cups_get_classes_schema,
15 | cups_get_devices_schema,
16 | cups_get_ppd_schema,
17 | cups_get_ppds_schema,
18 | cups_get_printers_schema,
19 | cups_move_job_schema,
20 | cups_reject_jobs_schema,
21 | get_job_attributes_schema,
22 | get_jobs_schema,
23 | get_printer_attributes_schema,
24 | get_subscriptions_schema,
25 | get_notifications_schema,
26 | pause_printer_schema,
27 | print_job_schema,
28 | resume_printer_schema,
29 | send_document_schema,
30 | hold_new_jobs_schema,
31 | release_held_new_jobs_schema,
32 | cancel_subscription_schema,
33 | )
34 |
35 | default_dir = resource_filename('pyipptool', 'templates/')
36 | renderer = deform.template.ZPTRendererFactory((default_dir,))
37 |
38 | deform.Form.set_default_renderer(renderer)
39 |
40 |
41 | cancel_job_form = deform.Form(cancel_job_schema)
42 | release_job_form = deform.Form(release_job_schema)
43 | create_job_form = deform.Form(create_job_schema)
44 | create_job_subscription_form = deform.Form(
45 | create_job_subscription_schema)
46 | create_printer_subscription_form = deform.Form(
47 | create_printer_subscription_schema)
48 | cups_add_modify_printer_form = deform.Form(cups_add_modify_printer_schema)
49 | cups_delete_printer_form = deform.Form(cups_delete_printer_schema)
50 | cups_delete_class_form = deform.Form(cups_delete_class_schema)
51 | cups_add_modify_class_form = deform.Form(cups_add_modify_class_schema)
52 | cups_get_classes_form = deform.Form(cups_get_classes_schema)
53 | cups_get_devices_form = deform.Form(cups_get_devices_schema)
54 | cups_get_ppd_form = deform.Form(cups_get_ppd_schema)
55 | cups_get_ppds_form = deform.Form(cups_get_ppds_schema)
56 | cups_get_printers_form = deform.Form(cups_get_printers_schema)
57 | cups_move_job_form = deform.Form(cups_move_job_schema)
58 | cups_reject_jobs_form = deform.Form(cups_reject_jobs_schema)
59 | get_job_attributes_form = deform.Form(get_job_attributes_schema)
60 | get_jobs_form = deform.Form(get_jobs_schema)
61 | get_printer_attributes_form = deform.Form(get_printer_attributes_schema)
62 | get_subscriptions_form = deform.Form(get_subscriptions_schema)
63 | get_notifications_form = deform.Form(get_notifications_schema)
64 | pause_printer_form = deform.Form(pause_printer_schema)
65 | print_job_form = deform.Form(print_job_schema)
66 | resume_printer_form = deform.Form(resume_printer_schema)
67 | send_document_form = deform.Form(send_document_schema)
68 | hold_new_jobs_form = deform.Form(hold_new_jobs_schema)
69 | release_held_new_jobs_form = deform.Form(release_held_new_jobs_schema)
70 | cancel_subscription_form = deform.Form(cancel_subscription_schema)
71 |
--------------------------------------------------------------------------------
/tests/test_async_subprocess.py:
--------------------------------------------------------------------------------
1 | import os
2 | import socket
3 | import threading
4 | import time
5 | import sys
6 |
7 | from future import standard_library
8 | from future.utils import PY3
9 | with standard_library.hooks():
10 | import http.server
11 | import socketserver
12 |
13 | import pytest
14 | import tornado.testing
15 | from past import autotranslate
16 | autotranslate(['pkipplib'])
17 | from pkipplib import pkipplib
18 |
19 |
20 | TRAVIS_USER = os.getenv('TRAVIS_USER', 'travis')
21 | TRAVIS_BUILD_DIR = os.getenv('TRAVIS_BUILD_DIR')
22 |
23 |
24 | @pytest.mark.skipif(sys.version_info > (3,),
25 | reason='pkipplib is only python2 compatible')
26 | class AsyncSubprocessTestCase(tornado.testing.AsyncTestCase):
27 |
28 | ipptool_path = ('%s/ipptool-20130731/ipptool' % TRAVIS_BUILD_DIR if
29 | TRAVIS_BUILD_DIR else '/usr/bin/ipptool')
30 | config = {'ipptool_path': ipptool_path,
31 | 'login': TRAVIS_USER,
32 | 'password': 'travis',
33 | 'graceful_shutdown_time': 2,
34 | 'timeout': 5}
35 |
36 | @tornado.testing.gen_test
37 | def test_async_call(self):
38 | from pyipptool.core import AsyncIPPToolWrapper
39 | from pyipptool.forms import get_subscriptions_form
40 |
41 | class Handler(http.server.BaseHTTPRequestHandler):
42 | """
43 | HTTP Handler that will make ipptool waiting
44 | """
45 | protocol_version = 'HTTP/1.1'
46 |
47 | def do_POST(self):
48 | # return a real IPP Response thanks to pkipplib
49 | if PY3:
50 | content_length = int(self.headers.get('content-length'))
51 | else:
52 | content_length = int(
53 | self.headers.getheader('content-length'))
54 | ipp_request = pkipplib.IPPRequest(
55 | self.rfile.read(content_length))
56 | ipp_request.parse()
57 | try:
58 | self.send_response(200)
59 | self.send_header('Content-type', 'application/ipp')
60 | self.end_headers()
61 | ipp_response = pkipplib.IPPRequest(
62 | operation_id=pkipplib.IPP_OK,
63 | request_id=ipp_request.request_id)
64 | ipp_response.operation['attributes-charset'] =\
65 | ('charset', 'utf-8')
66 | ipp_response.operation['attributes-natural-language'] =\
67 | ('naturalLanguage', 'en-us')
68 | self.wfile.write(ipp_response.dump())
69 | finally:
70 | assassin = threading.Thread(target=self.server.shutdown)
71 | assassin.daemon = True
72 | assassin.start()
73 |
74 | PORT = 6789
75 | while True:
76 | try:
77 | httpd = socketserver.TCPServer(("", PORT), Handler)
78 | except socket.error as exe:
79 | if exe.errno in (48, 98):
80 | PORT += 1
81 | else:
82 | raise
83 | else:
84 | break
85 | httpd.allow_reuse_address = True
86 |
87 | thread = threading.Thread(target=httpd.serve_forever)
88 | thread.daemon = True
89 | thread.start()
90 |
91 | wrapper = AsyncIPPToolWrapper(self.config, self.io_loop)
92 | wrapper.config['cups_uri'] = 'http://localhost:%s/' % PORT
93 |
94 | request = get_subscriptions_form.render(
95 | {'operation_attributes_tag':
96 | {'printer_uri': 'http://localhost:%s/printers/fake' % PORT}}
97 | )
98 |
99 | response = yield wrapper._call_ipptool(request)
100 | for key in ('RequestId', 'ipptoolVersion', 'Version'):
101 | # May diverge depending of version of ipptool
102 | try:
103 | del response[key]
104 | except KeyError:
105 | pass
106 | expected_response = {'Name': 'Get Subscriptions',
107 | 'Operation': 'Get-Subscriptions',
108 | 'Successful': True,
109 | 'RequestAttributes':
110 | [{'attributes-charset': 'utf-8',
111 | 'attributes-natural-language': 'en',
112 | 'printer-uri':
113 | 'http://localhost:%s/printers/fake' % PORT}],
114 | 'ResponseAttributes':
115 | [{'attributes-charset': 'utf-8',
116 | 'attributes-natural-language': 'en-us'}],
117 | 'StatusCode': 'successful-ok',
118 | 'Successful': True}
119 | assert response == expected_response, response
120 |
121 | @tornado.testing.gen_test
122 | def test_async_timeout_call(self):
123 | from pyipptool.core import AsyncIPPToolWrapper, TimeoutError
124 | from pyipptool.forms import get_subscriptions_form
125 |
126 | class Handler(http.server.BaseHTTPRequestHandler):
127 | """
128 | HTTP Handler that will make ipptool waiting
129 | """
130 | protocol_version = 'HTTP/1.1'
131 |
132 | def do_POST(self):
133 | time.sleep(0.2)
134 | assassin = threading.Thread(target=self.server.shutdown)
135 | assassin.daemon = True
136 | assassin.start()
137 |
138 | PORT = 6789
139 | while True:
140 | try:
141 | httpd = socketserver.TCPServer(("", PORT), Handler)
142 | except socket.error as exe:
143 | if exe.errno in (48, 98):
144 | PORT += 1
145 | else:
146 | raise
147 | else:
148 | break
149 | httpd.allow_reuse_address = True
150 |
151 | thread = threading.Thread(target=httpd.serve_forever)
152 | thread.daemon = True
153 | thread.start()
154 |
155 | wrapper = AsyncIPPToolWrapper(self.config, self.io_loop)
156 | wrapper.config['cups_uri'] = 'http://localhost:%s/' % PORT
157 | request = get_subscriptions_form.render(
158 | {'header':
159 | {'operation_attributes':
160 | {'printer_uri': 'http://localhost:%s/printers/fake' % PORT}}}
161 | )
162 |
163 | try:
164 | old_timeout = wrapper.config['timeout']
165 | wrapper.config['timeout'] = .1
166 | with pytest.raises(TimeoutError):
167 | yield wrapper._call_ipptool(request)
168 | finally:
169 | wrapper.config['timeout'] = old_timeout
170 |
--------------------------------------------------------------------------------
/pyipptool/schemas.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import colander
4 | from .widgets import (IPPAttributeWidget,
5 | IPPBodyWidget,
6 | IPPFileWidget,
7 | IPPGroupWidget,
8 | IPPNameWidget)
9 |
10 |
11 | class IntegerOrTuple(colander.List):
12 | def serialize(self, node, appstruct):
13 | if isinstance(appstruct, (tuple, list)):
14 | return super(IntegerOrTuple, self).serialize(
15 | node,
16 | ','.join([str(i) for i in appstruct]))
17 | return super(IntegerOrTuple, self).serialize(node, appstruct)
18 |
19 |
20 | class StringOrTuple(colander.String):
21 | def serialize(self, node, appstruct):
22 | if isinstance(appstruct, (tuple, list)):
23 | return super(StringOrTuple, self).serialize(node,
24 | ','.join(appstruct))
25 | return super(StringOrTuple, self).serialize(node, appstruct)
26 |
27 |
28 | class Charset(colander.String):
29 | pass
30 |
31 |
32 | class Enum(colander.String):
33 | pass
34 |
35 |
36 | class Integer(IntegerOrTuple):
37 | pass
38 |
39 |
40 | class Keyword(StringOrTuple):
41 | pass
42 |
43 |
44 | class Language(colander.String):
45 | pass
46 |
47 |
48 | class MimeMediaType(colander.String):
49 | pass
50 |
51 |
52 | class Name(colander.String):
53 | pass
54 |
55 |
56 | class NaturalLanguage(colander.String):
57 | pass
58 |
59 |
60 | class RangeOfInteger(colander.String):
61 | pass
62 |
63 |
64 | class Resolution(colander.String):
65 | pass
66 |
67 |
68 | range_regex = re.compile('^(\d+)-(\d+)$')
69 |
70 |
71 | def range_of_integer_validator(node, value):
72 | match = range_regex.match(value)
73 | if match is None:
74 | raise colander.Invalid(node,
75 | "Invalid range e.g '1-5'")
76 | low, high = match.groups()
77 | if int(low) >= int(high):
78 | raise colander.Invalid(node,
79 | 'Low bound must be lower than high bound')
80 |
81 |
82 | class Text(colander.String):
83 | def serialize(self, node, appstruct):
84 | if appstruct is None:
85 | raise ValueError('None value provided for {!r}'.format(node.name))
86 | if appstruct is colander.null:
87 | return colander.null
88 | value = super(Text, self).serialize(node, appstruct)
89 | return '"{}"'.format(value)
90 |
91 |
92 | class Uri(StringOrTuple):
93 | pass
94 |
95 |
96 | class OperationAttributesGroup(colander.Schema):
97 | attributes_charset = colander.SchemaNode(Charset(),
98 | default='utf-8',
99 | widget=IPPAttributeWidget())
100 | attributes_natural_language = colander.SchemaNode(
101 | Language(),
102 | default='en',
103 | widget=IPPAttributeWidget())
104 | compression = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
105 | printer_uri = colander.SchemaNode(Uri(),
106 | widget=IPPAttributeWidget())
107 | job_uri = colander.SchemaNode(Uri(), widget=IPPAttributeWidget())
108 | job_id = colander.SchemaNode(colander.Integer(),
109 | widget=IPPAttributeWidget())
110 | exclude_schemes = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
111 | include_schemes = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
112 | limit = colander.SchemaNode(
113 | colander.Integer(),
114 | widget=IPPAttributeWidget())
115 | requested_attributes = colander.SchemaNode(
116 | Keyword(),
117 | widget=IPPAttributeWidget())
118 | purge_job = colander.SchemaNode(colander.Boolean(true_val=1, false_val=0),
119 | widget=IPPAttributeWidget())
120 | requesting_user_name = colander.SchemaNode(Name(),
121 | widget=IPPAttributeWidget())
122 | job_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
123 | ipp_attribute_fidelity = colander.SchemaNode(colander.Boolean(true_val=1,
124 | false_val=0),
125 | widget=IPPAttributeWidget())
126 | job_k_octets = colander.SchemaNode(Integer(), widget=IPPAttributeWidget())
127 | job_impressions = colander.SchemaNode(Integer(),
128 | widget=IPPAttributeWidget())
129 | job_media_sheets = colander.SchemaNode(Integer(),
130 | widget=IPPAttributeWidget())
131 | document_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
132 | device_class = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
133 | document_format = colander.SchemaNode(MimeMediaType(),
134 | widget=IPPAttributeWidget())
135 | document_natural_language = colander.SchemaNode(
136 | NaturalLanguage(),
137 | widget=IPPAttributeWidget())
138 |
139 | notify_subscription_id = colander.SchemaNode(
140 | colander.Integer(),
141 | widget=IPPAttributeWidget())
142 |
143 | printer_message_from_operator = colander.SchemaNode(
144 | Text(), widget=IPPAttributeWidget())
145 |
146 | limit = colander.SchemaNode(
147 | colander.Integer(),
148 | widget=IPPAttributeWidget())
149 | requested_attributes = colander.SchemaNode(
150 | Keyword(),
151 | widget=IPPAttributeWidget())
152 | which_jobs = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
153 | my_jobs = colander.SchemaNode(colander.Boolean(true_val=1, false_val=0),
154 | widget=IPPAttributeWidget())
155 | notify_job_id = colander.SchemaNode(
156 | colander.Integer(),
157 | widget=IPPAttributeWidget())
158 | limit = colander.SchemaNode(
159 | colander.Integer(),
160 | widget=IPPAttributeWidget())
161 | requested_attributes = colander.SchemaNode(
162 | Keyword(),
163 | widget=IPPAttributeWidget())
164 | my_subscriptions = colander.SchemaNode(
165 | colander.Boolean(false_val=0, true_val=1),
166 | widget=IPPAttributeWidget())
167 |
168 | notify_subscription_ids = colander.SchemaNode(
169 | Integer(),
170 | widget=IPPAttributeWidget())
171 | notify_sequence_numbers = colander.SchemaNode(
172 | Integer(),
173 | widget=IPPAttributeWidget())
174 | notify_wait = colander.SchemaNode(
175 | colander.Boolean(false_val=0, true_val=1),
176 | widget=IPPAttributeWidget())
177 |
178 | ppd_make = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
179 | ppd_make_and_model = colander.SchemaNode(
180 | Text(),
181 | widget=IPPAttributeWidget())
182 | ppd_model_number = colander.SchemaNode(colander.Integer(),
183 | widget=IPPAttributeWidget())
184 | ppd_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
185 | ppd_natural_language = colander.SchemaNode(NaturalLanguage(),
186 | widget=IPPAttributeWidget())
187 | ppd_product = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
188 | ppd_psversion = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
189 | ppd_type = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
190 |
191 | timeout = colander.SchemaNode(
192 | colander.Integer(),
193 | widget=IPPAttributeWidget())
194 |
195 | first_printer_name = colander.SchemaNode(
196 | Name(),
197 | widget=IPPAttributeWidget())
198 | printer_location = colander.SchemaNode(
199 | Text(),
200 | widget=IPPAttributeWidget())
201 | printer_type = colander.SchemaNode(Enum(), widget=IPPAttributeWidget())
202 | printer_type_mask = colander.SchemaNode(
203 | Enum(),
204 | widget=IPPAttributeWidget())
205 | requested_attributes = colander.SchemaNode(
206 | Keyword(),
207 | widget=IPPAttributeWidget())
208 | requested_user_name = colander.SchemaNode(Name(),
209 | widget=IPPAttributeWidget())
210 |
211 | document_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
212 | compression = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
213 | document_format = colander.SchemaNode(MimeMediaType(),
214 | widget=IPPAttributeWidget())
215 | document_natural_language = colander.SchemaNode(
216 | NaturalLanguage(),
217 | widget=IPPAttributeWidget())
218 | last_document = colander.SchemaNode(
219 | colander.Boolean(false_val=0, true_val=1),
220 | widget=IPPAttributeWidget())
221 |
222 |
223 | class JobAttributesGroup(colander.Schema):
224 | job_priority = colander.SchemaNode(Integer(),
225 | widget=IPPAttributeWidget())
226 | job_printer_uri = colander.SchemaNode(Uri(),
227 | widget=IPPAttributeWidget())
228 | job_hold_until = colander.SchemaNode(Keyword(),
229 | widget=IPPAttributeWidget())
230 | job_sheets = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
231 | auth_info = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
232 | job_billing = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
233 | multiple_document_handling = colander.SchemaNode(
234 | Keyword(),
235 | widget=IPPAttributeWidget())
236 | copies = colander.SchemaNode(Integer(), widget=IPPAttributeWidget())
237 | finishings = colander.SchemaNode(Enum(), widget=IPPAttributeWidget())
238 | page_ranges = colander.SchemaNode(RangeOfInteger(),
239 | widget=IPPAttributeWidget(),
240 | validator=range_of_integer_validator)
241 | sides = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
242 | number_up = colander.SchemaNode(Integer(), widget=IPPAttributeWidget())
243 | orientation_requested = colander.SchemaNode(Enum(),
244 | widget=IPPAttributeWidget())
245 | media = colander.SchemaNode(Keyword(), widget=IPPAttributeWidget())
246 | printer_resolution = colander.SchemaNode(Resolution(),
247 | widget=IPPAttributeWidget())
248 | print_quality = colander.SchemaNode(Enum(), widget=IPPAttributeWidget())
249 |
250 | # Arbitrary Job Attributes
251 | ezeep_job_uuid = colander.SchemaNode(Text(),
252 | widget=IPPAttributeWidget())
253 |
254 |
255 | class SubscriptionGroup(colander.Schema):
256 | notify_recipient_uri = colander.SchemaNode(Uri(),
257 | widget=IPPAttributeWidget())
258 | notify_events = colander.SchemaNode(Keyword(),
259 | widget=IPPAttributeWidget())
260 | notify_job_id = colander.SchemaNode(colander.Integer(),
261 | widget=IPPAttributeWidget())
262 | notify_pull_method = colander.SchemaNode(Keyword(),
263 | widget=IPPAttributeWidget())
264 | notify_attributes = colander.SchemaNode(Keyword(),
265 | widget=IPPAttributeWidget())
266 | notify_charset = colander.SchemaNode(Charset(),
267 | widget=IPPAttributeWidget())
268 | notify_natural_language = colander.SchemaNode(Language(),
269 | widget=IPPAttributeWidget())
270 | notify_time_interval = colander.SchemaNode(colander.Integer(),
271 | widget=IPPAttributeWidget())
272 | notify_lease_duration = colander.SchemaNode(colander.Integer(),
273 | widget=IPPAttributeWidget())
274 |
275 |
276 | class DocumentAttributesGroup(colander.Schema):
277 | file = colander.SchemaNode(colander.String(), widget=IPPFileWidget())
278 |
279 |
280 | class PrinterAttributesGroup(colander.Schema):
281 | auth_info_required = colander.SchemaNode(Keyword(),
282 | widget=IPPAttributeWidget())
283 |
284 | printer_is_accepting_jobs = colander.SchemaNode(
285 | colander.Boolean(false_val=0, true_val=1),
286 | widget=IPPAttributeWidget())
287 | printer_info = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
288 | printer_location = colander.SchemaNode(Text(), widget=IPPAttributeWidget())
289 | printer_more_info = colander.SchemaNode(Uri(), widget=IPPAttributeWidget())
290 | printer_op_policy = colander.SchemaNode(Name(),
291 | widget=IPPAttributeWidget())
292 | printer_state = colander.SchemaNode(Enum(), widget=IPPAttributeWidget())
293 | printer_state_message = colander.SchemaNode(
294 | Text(),
295 | widget=IPPAttributeWidget())
296 | requesting_user_name_allowed = colander.SchemaNode(
297 | Name(),
298 | widget=IPPAttributeWidget())
299 | requesting_user_name_denied = colander.SchemaNode(
300 | Name(),
301 | widget=IPPAttributeWidget())
302 | printer_is_shared = colander.SchemaNode(colander.Boolean(true_val=1,
303 | false_val=0),
304 | widget=IPPAttributeWidget())
305 | job_sheets_default = colander.SchemaNode(Name(),
306 | widget=IPPAttributeWidget())
307 | device_uri = colander.SchemaNode(Uri(),
308 | widget=IPPAttributeWidget())
309 | port_monitor = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
310 | ppd_name = colander.SchemaNode(Name(), widget=IPPAttributeWidget())
311 | member_uris = colander.SchemaNode(Uri(), widget=IPPAttributeWidget())
312 |
313 | # Always last
314 | file = colander.SchemaNode(colander.String(), widget=IPPFileWidget())
315 |
316 |
317 | class BaseIPPSchema(colander.Schema):
318 | name = colander.SchemaNode(colander.String(), widget=IPPNameWidget())
319 | operation = colander.SchemaNode(colander.String(), widget=IPPNameWidget())
320 | operation_attributes_tag = OperationAttributesGroup(
321 | widget=IPPGroupWidget())
322 |
323 |
324 | class CancelJobSchema(BaseIPPSchema):
325 | name = 'Cancel Job'
326 | operation = 'Cancel-Job'
327 |
328 |
329 | class ReleaseJobSchema(BaseIPPSchema):
330 | name = 'Release Job'
331 | operation = 'Release-Job'
332 |
333 |
334 | class CupsAddModifyPrinterSchema(BaseIPPSchema):
335 | name = 'CUPS Add Modify Printer'
336 | operation = 'CUPS-Add-Modify-Printer'
337 | printer_attributes_tag = PrinterAttributesGroup(widget=IPPGroupWidget())
338 |
339 |
340 | class CupsDeletePrinterSchema(BaseIPPSchema):
341 | name = 'CUPS Delete Printer'
342 | operation = 'CUPS-Delete-Printer'
343 |
344 |
345 | class CupsAddModifyClassSchema(BaseIPPSchema):
346 | name = 'CUPS Add Modify Class'
347 | operation = 'CUPS-Add-Modify-Class'
348 | printer_attributes_tag = PrinterAttributesGroup(widget=IPPGroupWidget())
349 |
350 |
351 | class CupsDeleteClassSchema(CupsDeletePrinterSchema):
352 | name = 'CUPS Delete Class'
353 | operation = 'CUPS-Delete-Class'
354 |
355 |
356 | class CupsGetClassesSchema(BaseIPPSchema):
357 | name = 'CUPS Get Classes'
358 | operation = 'CUPS-Get-Classes'
359 |
360 |
361 | class CupsGetDevicesSchema(BaseIPPSchema):
362 | name = 'CUPS Get Devices'
363 | operation = 'CUPS-Get-Devices'
364 |
365 |
366 | class CupsGetPPDSchema(BaseIPPSchema):
367 | name = 'CUPS Get PPD'
368 | operation = 'CUPS-Get-PPD'
369 |
370 |
371 | class CupsGetPPDsSchema(BaseIPPSchema):
372 | name = 'CUPS Get PPDs'
373 | operation = 'CUPS-Get-PPDs'
374 |
375 |
376 | class CupsGetPrintersSchema(CupsGetClassesSchema):
377 | name = 'CUPS Get Printers'
378 | operation = 'CUPS-Get-Printers'
379 |
380 |
381 | class CupsMoveJobSchema(BaseIPPSchema):
382 | name = 'CUPS Move Job'
383 | operation = 'CUPS-Move-Job'
384 | job_attributes_tag = JobAttributesGroup(widget=IPPGroupWidget())
385 |
386 |
387 | class CupsRejectJobsSchema(BaseIPPSchema):
388 | name = 'CUPS Reject Jobs'
389 | operation = 'CUPS-Reject-Jobs'
390 | printer_attributes_tag = PrinterAttributesGroup(widget=IPPGroupWidget())
391 |
392 |
393 | class CreateJobSchema(BaseIPPSchema):
394 | """
395 | http://www.cups.org/documentation.php/spec-ipp.html#CREATE_JOB
396 | """
397 | name = 'Create Job'
398 | operation = 'Create-Job'
399 | job_attributes_tag = JobAttributesGroup(widget=IPPGroupWidget())
400 |
401 |
402 | class CreateJobSubscriptionSchema(BaseIPPSchema):
403 | name = 'Create Job Subscription'
404 | operation = 'Create-Job-Subscription'
405 | subscription_attributes_tag = SubscriptionGroup(
406 | widget=IPPGroupWidget())
407 |
408 |
409 | class CreatePrinterSubscriptionSchema(CreateJobSubscriptionSchema):
410 | name = 'Create Printer Subscription'
411 | operation = 'Create-Printer-Subscription'
412 |
413 |
414 | class GetJobAttributesSchema(BaseIPPSchema):
415 | name = 'Get Job Attributes'
416 | operation = 'Get-Job-Attributes'
417 |
418 |
419 | class GetJobsSchema(BaseIPPSchema):
420 | name = 'Get Jobs'
421 | operation = 'Get-Jobs'
422 |
423 |
424 | class GetPrinterAttributesSchema(BaseIPPSchema):
425 | name = 'Get Printer Attributes'
426 | operation = 'Get-Printer-Attributes'
427 |
428 |
429 | class GetSubscriptionsSchema(BaseIPPSchema):
430 | name = 'Get Subscriptions'
431 | operation = 'Get-Subscriptions'
432 |
433 |
434 | class GetNotificationsSchema(BaseIPPSchema):
435 | name = 'Get Notifications'
436 | operation = 'Get-Notifications'
437 |
438 |
439 | class PausePrinterSchema(BaseIPPSchema):
440 | name = 'Pause Printer'
441 | operation = 'Pause-Printer'
442 |
443 |
444 | class PrintJobSchema(CreateJobSchema):
445 | name = 'Print Job'
446 | operation = 'Print-Job'
447 | job_attributes_tag = JobAttributesGroup(widget=IPPGroupWidget())
448 | subscription_attributes_tag = SubscriptionGroup(
449 | widget=IPPGroupWidget())
450 | document_attributes_tag = DocumentAttributesGroup(widget=IPPGroupWidget())
451 |
452 |
453 | class ResumePrinterSchema(PausePrinterSchema):
454 | name = 'Resume Printer'
455 | operation = 'Resume-Printer'
456 |
457 |
458 | class SendDocumentSchema(BaseIPPSchema):
459 | name = 'Send Document'
460 | operation = 'Send-Document'
461 | document_attributes_tag = DocumentAttributesGroup(widget=IPPGroupWidget())
462 |
463 |
464 | class HoldNewJobsSchema(PausePrinterSchema):
465 | name = 'Hold New Jobs'
466 | operation = 'Hold-New-Jobs'
467 |
468 |
469 | class ReleaseHeldNewJobsSchema(HoldNewJobsSchema):
470 | name = 'Release Held New Jobs'
471 | operation = 'Release-Held-New-Jobs'
472 |
473 |
474 | class CancelSubscriptionSchema(BaseIPPSchema):
475 | name = 'Cancel Subscription'
476 | operation = 'Cancel-Subscription'
477 |
478 |
479 | cancel_job_schema = CancelJobSchema(widget=IPPBodyWidget())
480 |
481 | release_job_schema = ReleaseJobSchema(widget=IPPBodyWidget())
482 |
483 | create_job_subscription_schema = CreateJobSubscriptionSchema(
484 | widget=IPPBodyWidget())
485 |
486 | create_job_schema = CreateJobSchema(widget=IPPBodyWidget())
487 |
488 | create_printer_subscription_schema = CreatePrinterSubscriptionSchema(
489 | widget=IPPBodyWidget())
490 |
491 | cups_add_modify_class_schema = CupsAddModifyClassSchema(
492 | widget=IPPBodyWidget())
493 |
494 | cups_add_modify_printer_schema = CupsAddModifyPrinterSchema(
495 | widget=IPPBodyWidget())
496 |
497 | cups_delete_printer_schema = CupsDeletePrinterSchema(
498 | widget=IPPBodyWidget())
499 |
500 | cups_delete_class_schema = CupsDeleteClassSchema(
501 | widget=IPPBodyWidget())
502 |
503 | cups_get_classes_schema = CupsGetClassesSchema(widget=IPPBodyWidget())
504 |
505 | cups_get_devices_schema = CupsGetDevicesSchema(widget=IPPBodyWidget())
506 |
507 | cups_get_ppd_schema = CupsGetPPDSchema(widget=IPPBodyWidget())
508 |
509 | cups_get_ppds_schema = CupsGetPPDsSchema(widget=IPPBodyWidget())
510 |
511 | cups_get_printers_schema = CupsGetPrintersSchema(widget=IPPBodyWidget())
512 |
513 | cups_move_job_schema = CupsMoveJobSchema(widget=IPPBodyWidget())
514 |
515 | cups_reject_jobs_schema = CupsRejectJobsSchema(widget=IPPBodyWidget())
516 |
517 | get_job_attributes_schema = GetJobAttributesSchema(widget=IPPBodyWidget())
518 |
519 | get_jobs_schema = GetJobsSchema(widget=IPPBodyWidget())
520 |
521 | get_printer_attributes_schema = GetPrinterAttributesSchema(
522 | widget=IPPBodyWidget())
523 |
524 | get_subscriptions_schema = GetSubscriptionsSchema(widget=IPPBodyWidget())
525 |
526 | get_notifications_schema = GetNotificationsSchema(widget=IPPBodyWidget())
527 |
528 | pause_printer_schema = PausePrinterSchema(widget=IPPBodyWidget())
529 |
530 | print_job_schema = PrintJobSchema(widget=IPPBodyWidget())
531 |
532 | resume_printer_schema = ResumePrinterSchema(widget=IPPBodyWidget())
533 |
534 | send_document_schema = SendDocumentSchema(widget=IPPBodyWidget())
535 |
536 | hold_new_jobs_schema = HoldNewJobsSchema(widget=IPPBodyWidget())
537 |
538 | release_held_new_jobs_schema = ReleaseHeldNewJobsSchema(widget=IPPBodyWidget())
539 |
540 | cancel_subscription_schema = CancelSubscriptionSchema(widget=IPPBodyWidget())
541 |
--------------------------------------------------------------------------------
/tests/test_highlevel.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import socket
3 | import tempfile
4 | import textwrap
5 | import threading
6 | import time
7 |
8 | from future import standard_library
9 | with standard_library.hooks():
10 | import http.server
11 | import socketserver
12 |
13 | import mock
14 | import pytest
15 |
16 | import pyipptool
17 |
18 |
19 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
20 | def test_ipptool_create_job_subscription_pull_delivery_method(_call_ipptool):
21 | from pyipptool import create_job_subscription
22 | create_job_subscription(
23 | printer_uri='https://localhost:631/printer/p',
24 | requesting_user_name='admin',
25 | notify_job_id=108,
26 | notify_recipient_uri='rss://',
27 | notify_events=('job-completed', 'job-created', 'job-progress'),
28 | notify_attributes='notify-subscriber-user-name',
29 | notify_charset='utf-8',
30 | notify_natural_language='de',
31 | notify_time_interval=1)
32 | request = _call_ipptool._mock_mock_calls[0][1][-1]
33 | expected_request = textwrap.dedent("""
34 | {
35 | NAME "Create Job Subscription"
36 | OPERATION "Create-Job-Subscription"
37 | GROUP operation-attributes-tag
38 | ATTR charset attributes-charset utf-8
39 | ATTR language attributes-natural-language en
40 | ATTR uri printer-uri https://localhost:631/printer/p
41 | ATTR name requesting-user-name admin
42 | GROUP subscription-attributes-tag
43 | ATTR uri notify-recipient-uri rss://
44 | ATTR keyword notify-events job-completed,job-created,job-progress
45 | ATTR integer notify-job-id 108
46 | ATTR keyword notify-attributes notify-subscriber-user-name
47 | ATTR charset notify-charset utf-8
48 | ATTR language notify-natural-language de
49 | ATTR integer notify-time-interval 1
50 | }""").strip()
51 | assert request == expected_request, request
52 |
53 |
54 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
55 | def test_ipptool_create_printer_subscription(_call_ipptool):
56 | from pyipptool import create_printer_subscription
57 | create_printer_subscription(
58 | printer_uri='https://localhost:631/classes/PUBLIC-PDF',
59 | requesting_user_name='admin',
60 | notify_recipient_uri='rss://',
61 | notify_events='all',
62 | notify_attributes='notify-subscriber-user-name',
63 | notify_charset='utf-8',
64 | notify_natural_language='de',
65 | notify_lease_duration=0,
66 | notify_time_interval=1)
67 | request = _call_ipptool._mock_mock_calls[0][1][-1]
68 | expected_request = textwrap.dedent("""
69 | {
70 | NAME "Create Printer Subscription"
71 | OPERATION "Create-Printer-Subscription"
72 | GROUP operation-attributes-tag
73 | ATTR charset attributes-charset utf-8
74 | ATTR language attributes-natural-language en
75 | ATTR uri printer-uri https://localhost:631/classes/PUBLIC-PDF
76 | ATTR name requesting-user-name admin
77 | GROUP subscription-attributes-tag
78 | ATTR uri notify-recipient-uri rss://
79 | ATTR keyword notify-events all
80 | ATTR keyword notify-attributes notify-subscriber-user-name
81 | ATTR charset notify-charset utf-8
82 | ATTR language notify-natural-language de
83 | ATTR integer notify-time-interval 1
84 | ATTR integer notify-lease-duration 0
85 | }""").strip()
86 | assert request == expected_request, request
87 |
88 |
89 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
90 | def test_cups_add_modify_printer(_call_ipptool):
91 | from pyipptool import cups_add_modify_printer
92 | _call_ipptool.return_value = {'Tests': [{}]}
93 | cups_add_modify_printer(
94 | printer_uri='https://localhost:631/classes/PUBLIC-PDF',
95 | device_uri='cups-pdf:/',
96 | printer_is_shared=False,
97 | )
98 | request = _call_ipptool._mock_mock_calls[0][1][-1]
99 | expected_request = textwrap.dedent("""
100 | {
101 | NAME "CUPS Add Modify Printer"
102 | OPERATION "CUPS-Add-Modify-Printer"
103 | GROUP operation-attributes-tag
104 | ATTR charset attributes-charset utf-8
105 | ATTR language attributes-natural-language en
106 | ATTR uri printer-uri https://localhost:631/classes/PUBLIC-PDF
107 | GROUP printer-attributes-tag
108 | ATTR boolean printer-is-shared 0
109 | ATTR uri device-uri cups-pdf:/
110 | }""").strip()
111 | assert request == expected_request, request
112 |
113 |
114 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
115 | def test_cups_add_modify_printer_with_ppd(_call_ipptool):
116 | from pyipptool import cups_add_modify_printer
117 | _call_ipptool.return_value = {'Tests': [{}]}
118 | with tempfile.NamedTemporaryFile('rb') as tmp:
119 | cups_add_modify_printer(
120 | printer_uri='https://localhost:631/classes/PUBLIC-PDF',
121 | device_uri='cups-pdf:/',
122 | printer_is_shared=False,
123 | ppd_content=tmp,
124 | )
125 | request = _call_ipptool._mock_mock_calls[0][1][-1]
126 | expected_request = textwrap.dedent("""
127 | {
128 | NAME "CUPS Add Modify Printer"
129 | OPERATION "CUPS-Add-Modify-Printer"
130 | GROUP operation-attributes-tag
131 | ATTR charset attributes-charset utf-8
132 | ATTR language attributes-natural-language en
133 | ATTR uri printer-uri https://localhost:631/classes/PUBLIC-PDF
134 | GROUP printer-attributes-tag
135 | ATTR boolean printer-is-shared 0
136 | ATTR uri device-uri cups-pdf:/
137 | FILE %s
138 | }""" % tmp.name).strip()
139 | assert request == expected_request, request
140 |
141 |
142 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
143 | def test_get_job_attributes_with_job_id(_call_ipptool):
144 | from pyipptool import get_job_attributes
145 | get_job_attributes(
146 | printer_uri='https://localhost:631/classes/PUBLIC-PDF',
147 | job_id=2)
148 | request = _call_ipptool._mock_mock_calls[0][1][0]
149 | expected_request = textwrap.dedent("""
150 | {
151 | NAME "Get Job Attributes"
152 | OPERATION "Get-Job-Attributes"
153 | GROUP operation-attributes-tag
154 | ATTR charset attributes-charset utf-8
155 | ATTR language attributes-natural-language en
156 | ATTR uri printer-uri https://localhost:631/classes/PUBLIC-PDF
157 | ATTR integer job-id 2
158 | }""").strip()
159 | assert request == expected_request, request
160 |
161 |
162 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
163 | def test_get_job_attributes_with_job_uri(_call_ipptool):
164 | from pyipptool import get_job_attributes
165 | get_job_attributes(
166 | job_uri='https://localhost:631/jobs/2')
167 | request = _call_ipptool._mock_mock_calls[0][1][0]
168 | expected_request = textwrap.dedent("""
169 | {
170 | NAME "Get Job Attributes"
171 | OPERATION "Get-Job-Attributes"
172 | GROUP operation-attributes-tag
173 | ATTR charset attributes-charset utf-8
174 | ATTR language attributes-natural-language en
175 | ATTR uri job-uri https://localhost:631/jobs/2
176 | }""").strip()
177 | assert request == expected_request, request
178 |
179 |
180 | def test_timeout():
181 | from pyipptool import wrapper
182 | from pyipptool.core import TimeoutError
183 | from pyipptool.forms import get_subscriptions_form
184 |
185 | class Handler(http.server.BaseHTTPRequestHandler):
186 | """
187 | HTTP Handler that will make ipptool waiting
188 | """
189 |
190 | def do_POST(self):
191 | time.sleep(.2)
192 | assassin = threading.Thread(target=self.server.shutdown)
193 | assassin.daemon = True
194 | assassin.start()
195 |
196 | PORT = 6789
197 | while True:
198 | try:
199 | httpd = socketserver.TCPServer(("", PORT), Handler)
200 | except socket.error as exe:
201 | if exe.errno in (48, 98):
202 | PORT += 1
203 | else:
204 | raise
205 | else:
206 | break
207 | httpd.allow_reuse_address = True
208 |
209 | thread = threading.Thread(target=httpd.serve_forever)
210 | thread.daemon = True
211 | thread.start()
212 |
213 | request = get_subscriptions_form.render(
214 | {'header':
215 | {'operation_attributes':
216 | {'printer_uri':
217 | 'http://localhost:%s/printers/fake' % PORT}}})
218 |
219 | old_timeout = wrapper.config['timeout']
220 | wrapper.config['timeout'] = .1
221 | wrapper.config['cups_uri'] = 'http://localhost:%s/' % PORT
222 | try:
223 | with pytest.raises(TimeoutError):
224 | wrapper._call_ipptool(request)
225 | finally:
226 | wrapper.config['timeout'] = old_timeout
227 |
228 |
229 | def test_authentication():
230 | from pyipptool import IPPToolWrapper
231 | wrapper = IPPToolWrapper({'login': 'ezeep',
232 | 'password': 'secret',
233 | 'cups_uri': 'http://localhost:631/'
234 | 'printers/?arg=value'})
235 |
236 | assert (wrapper.authenticated_uri == 'http://ezeep:secret@localhost:631/'
237 | 'printers/?arg=value')
238 |
239 |
240 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
241 | def test_release_job(_call_ipptool):
242 | from pyipptool import release_job
243 | _call_ipptool.return_value = {'Tests': [{}]}
244 | release_job(job_uri='ipp://cups:631/jobs/3')
245 | request = _call_ipptool._mock_mock_calls[0][1][0]
246 | expected_request = textwrap.dedent("""
247 | {
248 | NAME "Release Job"
249 | OPERATION "Release-Job"
250 | GROUP operation-attributes-tag
251 | ATTR charset attributes-charset utf-8
252 | ATTR language attributes-natural-language en
253 | ATTR uri job-uri ipp://cups:631/jobs/3
254 | }""").strip()
255 | assert request == expected_request, request
256 |
257 |
258 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
259 | def test_cancel_job(_call_ipptool):
260 | from pyipptool import cancel_job
261 | _call_ipptool.return_value = {'Tests': [{}]}
262 | cancel_job(job_uri='ipp://cups:631/jobs/12')
263 | request = _call_ipptool._mock_mock_calls[0][1][0]
264 | expected_request = textwrap.dedent("""
265 | {
266 | NAME "Cancel Job"
267 | OPERATION "Cancel-Job"
268 | GROUP operation-attributes-tag
269 | ATTR charset attributes-charset utf-8
270 | ATTR language attributes-natural-language en
271 | ATTR uri job-uri ipp://cups:631/jobs/12
272 | }""").strip()
273 | assert request == expected_request, request
274 |
275 |
276 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
277 | def test_cups_add_modify_class(_call_ipptool):
278 | from pyipptool import cups_add_modify_class
279 | _call_ipptool.return_value = {'Tests': [{}]}
280 | cups_add_modify_class(printer_uri='ipp://cups:631/classes/p',
281 | printer_is_shared=True)
282 | request = _call_ipptool._mock_mock_calls[0][1][0]
283 | expected_request = textwrap.dedent("""
284 | {
285 | NAME "CUPS Add Modify Class"
286 | OPERATION "CUPS-Add-Modify-Class"
287 | GROUP operation-attributes-tag
288 | ATTR charset attributes-charset utf-8
289 | ATTR language attributes-natural-language en
290 | ATTR uri printer-uri ipp://cups:631/classes/p
291 | GROUP printer-attributes-tag
292 | ATTR boolean printer-is-shared 1
293 | }""").strip()
294 | assert request == expected_request, request
295 |
296 |
297 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
298 | def test_cups_delete_printer(_call_ipptool):
299 | from pyipptool import cups_delete_printer
300 | _call_ipptool.return_value = {'Tests': [{}]}
301 | cups_delete_printer(printer_uri='ipp://cups:631/printers/p')
302 | request = _call_ipptool._mock_mock_calls[0][1][0]
303 | expected_request = textwrap.dedent("""
304 | {
305 | NAME "CUPS Delete Printer"
306 | OPERATION "CUPS-Delete-Printer"
307 | GROUP operation-attributes-tag
308 | ATTR charset attributes-charset utf-8
309 | ATTR language attributes-natural-language en
310 | ATTR uri printer-uri ipp://cups:631/printers/p
311 | }""").strip()
312 | assert request == expected_request, request
313 |
314 |
315 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
316 | def test_cups_delete_class(_call_ipptool):
317 | from pyipptool import cups_delete_class
318 | _call_ipptool.return_value = {'Tests': [{}]}
319 | cups_delete_class(printer_uri='ipp://cups:631/classes/p')
320 | request = _call_ipptool._mock_mock_calls[0][1][0]
321 | expected_request = textwrap.dedent("""
322 | {
323 | NAME "CUPS Delete Class"
324 | OPERATION "CUPS-Delete-Class"
325 | GROUP operation-attributes-tag
326 | ATTR charset attributes-charset utf-8
327 | ATTR language attributes-natural-language en
328 | ATTR uri printer-uri ipp://cups:631/classes/p
329 | }""").strip()
330 | assert request == expected_request, request
331 |
332 |
333 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
334 | def test_cups_get_classes(_call_ipptool):
335 | from pyipptool import cups_get_classes
336 | _call_ipptool.return_value = {'Tests': [{}]}
337 | cups_get_classes()
338 | request = _call_ipptool._mock_mock_calls[0][1][0]
339 | expected_request = textwrap.dedent("""
340 | {
341 | NAME "CUPS Get Classes"
342 | OPERATION "CUPS-Get-Classes"
343 | GROUP operation-attributes-tag
344 | ATTR charset attributes-charset utf-8
345 | ATTR language attributes-natural-language en
346 | }""").strip()
347 | assert request == expected_request, request
348 |
349 |
350 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
351 | def test_cups_get_printers(_call_ipptool):
352 | from pyipptool import cups_get_printers
353 | _call_ipptool.return_value = {'Tests': [{}]}
354 | cups_get_printers()
355 | request = _call_ipptool._mock_mock_calls[0][1][0]
356 | expected_request = textwrap.dedent("""
357 | {
358 | NAME "CUPS Get Printers"
359 | OPERATION "CUPS-Get-Printers"
360 | GROUP operation-attributes-tag
361 | ATTR charset attributes-charset utf-8
362 | ATTR language attributes-natural-language en
363 | }""").strip()
364 | assert request == expected_request, request
365 |
366 |
367 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
368 | def test_cups_get_devices(_call_ipptool):
369 | from pyipptool import cups_get_devices
370 | _call_ipptool.return_value = {'Tests': [{}]}
371 | cups_get_devices()
372 | request = _call_ipptool._mock_mock_calls[0][1][0]
373 | expected_request = textwrap.dedent("""
374 | {
375 | NAME "CUPS Get Devices"
376 | OPERATION "CUPS-Get-Devices"
377 | GROUP operation-attributes-tag
378 | ATTR charset attributes-charset utf-8
379 | ATTR language attributes-natural-language en
380 | }""").strip()
381 | assert request == expected_request, request
382 |
383 |
384 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
385 | def test_cups_get_ppd_with_printer_uri(_call_ipptool):
386 | from pyipptool import cups_get_ppd
387 | _call_ipptool.return_value = {'Tests': [{}]}
388 | cups_get_ppd(printer_uri='ipp://cups:631/printers/p')
389 | request = _call_ipptool._mock_mock_calls[0][1][0]
390 | expected_request = textwrap.dedent("""
391 | {
392 | NAME "CUPS Get PPD"
393 | OPERATION "CUPS-Get-PPD"
394 | GROUP operation-attributes-tag
395 | ATTR charset attributes-charset utf-8
396 | ATTR language attributes-natural-language en
397 | ATTR uri printer-uri ipp://cups:631/printers/p
398 | }""").strip()
399 | assert request == expected_request, request
400 |
401 |
402 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
403 | def test_cups_get_ppd_with_ppd_name(_call_ipptool):
404 | from pyipptool import cups_get_ppd
405 | _call_ipptool.return_value = {'Tests': [{}]}
406 | cups_get_ppd(ppd_name='ppd-for-my-printer')
407 | request = _call_ipptool._mock_mock_calls[0][1][0]
408 | expected_request = textwrap.dedent("""
409 | {
410 | NAME "CUPS Get PPD"
411 | OPERATION "CUPS-Get-PPD"
412 | GROUP operation-attributes-tag
413 | ATTR charset attributes-charset utf-8
414 | ATTR language attributes-natural-language en
415 | ATTR name ppd-name ppd-for-my-printer
416 | }""").strip()
417 | assert request == expected_request, request
418 |
419 |
420 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
421 | def test_cups_get_ppds(_call_ipptool):
422 | from pyipptool import cups_get_ppds
423 | _call_ipptool.return_value = {'Tests': [{}]}
424 | cups_get_ppds()
425 | request = _call_ipptool._mock_mock_calls[0][1][0]
426 | expected_request = textwrap.dedent("""
427 | {
428 | NAME "CUPS Get PPDs"
429 | OPERATION "CUPS-Get-PPDs"
430 | GROUP operation-attributes-tag
431 | ATTR charset attributes-charset utf-8
432 | ATTR language attributes-natural-language en
433 | }""").strip()
434 | assert request == expected_request, request
435 |
436 |
437 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
438 | def test_cups_move_job(_call_ipptool):
439 | from pyipptool import cups_move_job
440 | _call_ipptool.return_value = {'Tests': [{}]}
441 | cups_move_job(job_uri='ipp://cups:631/jobs/12',
442 | job_printer_uri='ipp://cups:631/printers/p')
443 | request = _call_ipptool._mock_mock_calls[0][1][0]
444 | expected_request = textwrap.dedent("""
445 | {
446 | NAME "CUPS Move Job"
447 | OPERATION "CUPS-Move-Job"
448 | GROUP operation-attributes-tag
449 | ATTR charset attributes-charset utf-8
450 | ATTR language attributes-natural-language en
451 | ATTR uri job-uri ipp://cups:631/jobs/12
452 | GROUP job-attributes-tag
453 | ATTR uri job-printer-uri ipp://cups:631/printers/p
454 | }""").strip()
455 | assert request == expected_request, request
456 |
457 |
458 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
459 | def test_cups_reject_jobs(_call_ipptool):
460 | from pyipptool import cups_reject_jobs
461 | _call_ipptool.return_value = {'Tests': [{}]}
462 | cups_reject_jobs(printer_uri='ipp://cups:631/printers/p',
463 | requesting_user_name='boby')
464 | request = _call_ipptool._mock_mock_calls[0][1][0]
465 | expected_request = textwrap.dedent("""
466 | {
467 | NAME "CUPS Reject Jobs"
468 | OPERATION "CUPS-Reject-Jobs"
469 | GROUP operation-attributes-tag
470 | ATTR charset attributes-charset utf-8
471 | ATTR language attributes-natural-language en
472 | ATTR uri printer-uri ipp://cups:631/printers/p
473 | ATTR name requesting-user-name boby
474 | GROUP printer-attributes-tag
475 | }""").strip()
476 | assert request == expected_request, request
477 |
478 |
479 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
480 | def test_get_jobs(_call_ipptool):
481 | from pyipptool import get_jobs
482 | _call_ipptool.return_value = {'Tests': [{}]}
483 | get_jobs(printer_uri='ipp://cups:631/printers/p')
484 | request = _call_ipptool._mock_mock_calls[0][1][0]
485 | expected_request = textwrap.dedent("""
486 | {
487 | NAME "Get Jobs"
488 | OPERATION "Get-Jobs"
489 | GROUP operation-attributes-tag
490 | ATTR charset attributes-charset utf-8
491 | ATTR language attributes-natural-language en
492 | ATTR uri printer-uri ipp://cups:631/printers/p
493 | }""").strip()
494 | assert request == expected_request, request
495 |
496 |
497 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
498 | def test_get_printer_attributes(_call_ipptool):
499 | from pyipptool import get_printer_attributes
500 | _call_ipptool.return_value = {'Tests': [{}]}
501 | get_printer_attributes(printer_uri='ipp://cups:631/printers/p')
502 | request = _call_ipptool._mock_mock_calls[0][1][0]
503 | expected_request = textwrap.dedent("""
504 | {
505 | NAME "Get Printer Attributes"
506 | OPERATION "Get-Printer-Attributes"
507 | GROUP operation-attributes-tag
508 | ATTR charset attributes-charset utf-8
509 | ATTR language attributes-natural-language en
510 | ATTR uri printer-uri ipp://cups:631/printers/p
511 | }""").strip()
512 | assert request == expected_request, request
513 |
514 |
515 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
516 | def test_get_subscriptions(_call_ipptool):
517 | from pyipptool import get_subscriptions
518 | _call_ipptool.return_value = {'Tests': [{}]}
519 | get_subscriptions(printer_uri='ipp://cups:631/printers/p')
520 | request = _call_ipptool._mock_mock_calls[0][1][0]
521 | expected_request = textwrap.dedent("""
522 | {
523 | NAME "Get Subscriptions"
524 | OPERATION "Get-Subscriptions"
525 | GROUP operation-attributes-tag
526 | ATTR charset attributes-charset utf-8
527 | ATTR language attributes-natural-language en
528 | ATTR uri printer-uri ipp://cups:631/printers/p
529 | }""").strip()
530 | assert request == expected_request, request
531 |
532 |
533 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
534 | def test_get_notifications(_call_ipptool):
535 | from pyipptool import get_notifications
536 | _call_ipptool.return_value = {'Tests': [{}]}
537 | get_notifications(printer_uri='ipp://cups:631/printers/p',
538 | notify_subscription_ids=3)
539 | request = _call_ipptool._mock_mock_calls[0][1][0]
540 | expected_request = textwrap.dedent("""
541 | {
542 | NAME "Get Notifications"
543 | OPERATION "Get-Notifications"
544 | GROUP operation-attributes-tag
545 | ATTR charset attributes-charset utf-8
546 | ATTR language attributes-natural-language en
547 | ATTR uri printer-uri ipp://cups:631/printers/p
548 | ATTR integer notify-subscription-ids 3
549 | }""").strip()
550 | assert request == expected_request, request
551 |
552 |
553 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
554 | def test_pause_printer(_call_ipptool):
555 | from pyipptool import pause_printer
556 | _call_ipptool.return_value = {'Tests': [{}]}
557 | pause_printer(printer_uri='ipp://cups:631/printers/p')
558 | request = _call_ipptool._mock_mock_calls[0][1][0]
559 | expected_request = textwrap.dedent("""
560 | {
561 | NAME "Pause Printer"
562 | OPERATION "Pause-Printer"
563 | GROUP operation-attributes-tag
564 | ATTR charset attributes-charset utf-8
565 | ATTR language attributes-natural-language en
566 | ATTR uri printer-uri ipp://cups:631/printers/p
567 | }""").strip()
568 | assert request == expected_request, request
569 |
570 |
571 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
572 | def test_hold_new_jobs(_call_ipptool):
573 | from pyipptool import hold_new_jobs
574 | _call_ipptool.return_value = {'Tests': [{}]}
575 | hold_new_jobs(printer_uri='ipp://cups:631/printers/p')
576 | request = _call_ipptool._mock_mock_calls[0][1][0]
577 | expected_request = textwrap.dedent("""
578 | {
579 | NAME "Hold New Jobs"
580 | OPERATION "Hold-New-Jobs"
581 | GROUP operation-attributes-tag
582 | ATTR charset attributes-charset utf-8
583 | ATTR language attributes-natural-language en
584 | ATTR uri printer-uri ipp://cups:631/printers/p
585 | }""").strip()
586 | assert request == expected_request, request
587 |
588 |
589 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
590 | def test_release_held_new_jobs(_call_ipptool):
591 | from pyipptool import release_held_new_jobs
592 | _call_ipptool.return_value = {'Tests': [{}]}
593 | release_held_new_jobs(printer_uri='ipp://cups:631/printers/p')
594 | request = _call_ipptool._mock_mock_calls[0][1][0]
595 | expected_request = textwrap.dedent("""
596 | {
597 | NAME "Release Held New Jobs"
598 | OPERATION "Release-Held-New-Jobs"
599 | GROUP operation-attributes-tag
600 | ATTR charset attributes-charset utf-8
601 | ATTR language attributes-natural-language en
602 | ATTR uri printer-uri ipp://cups:631/printers/p
603 | }""").strip()
604 | assert request == expected_request, request
605 |
606 |
607 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
608 | def test_resume_printer(_call_ipptool):
609 | from pyipptool import resume_printer
610 | _call_ipptool.return_value = {'Tests': [{}]}
611 | resume_printer(printer_uri='ipp://cups:631/printers/p')
612 | request = _call_ipptool._mock_mock_calls[0][1][0]
613 | expected_request = textwrap.dedent("""
614 | {
615 | NAME "Resume Printer"
616 | OPERATION "Resume-Printer"
617 | GROUP operation-attributes-tag
618 | ATTR charset attributes-charset utf-8
619 | ATTR language attributes-natural-language en
620 | ATTR uri printer-uri ipp://cups:631/printers/p
621 | }""").strip()
622 | assert request == expected_request, request
623 |
624 |
625 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
626 | def test_cancel_subscription(_call_ipptool):
627 | from pyipptool import cancel_subscription
628 | _call_ipptool.return_value = {'Tests': [{}]}
629 | cancel_subscription(printer_uri='ipp://cups:631/printers/p',
630 | notify_subscription_id=3)
631 | request = _call_ipptool._mock_mock_calls[0][1][0]
632 | expected_request = textwrap.dedent("""
633 | {
634 | NAME "Cancel Subscription"
635 | OPERATION "Cancel-Subscription"
636 | GROUP operation-attributes-tag
637 | ATTR charset attributes-charset utf-8
638 | ATTR language attributes-natural-language en
639 | ATTR uri printer-uri ipp://cups:631/printers/p
640 | ATTR integer notify-subscription-id 3
641 | }""").strip()
642 | assert request == expected_request, request
643 |
644 |
645 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
646 | def test_create_job(_call_ipptool):
647 | from pyipptool import create_job
648 | _call_ipptool.return_value = {'Tests': [{}]}
649 | create_job(printer_uri='ipp://cups:631/classes/p',
650 | job_name='foo',
651 | job_priority=1,
652 | job_hold_until='indefinite',
653 | job_sheets='standard',
654 | multiple_document_handling='single-document',
655 | copies=2,
656 | finishings='punch',
657 | page_ranges='1-6',
658 | sides='two-sided-short-edge',
659 | number_up=4,
660 | orientation_requested='reverse-landscape',
661 | media='iso-a4-white',
662 | printer_resolution='600dpi',
663 | print_quality='5',
664 | ipp_attribute_fidelity=False,
665 | job_k_octets=1024,
666 | job_impressions=2048,
667 | job_media_sheets=2,
668 | auth_info='michael',
669 | job_billing='no-idea',
670 | )
671 | request = _call_ipptool._mock_mock_calls[0][1][-1]
672 | expected_request = textwrap.dedent("""
673 | {
674 | NAME "Create Job"
675 | OPERATION "Create-Job"
676 | GROUP operation-attributes-tag
677 | ATTR charset attributes-charset utf-8
678 | ATTR language attributes-natural-language en
679 | ATTR uri printer-uri ipp://cups:631/classes/p
680 | ATTR name job-name foo
681 | ATTR boolean ipp-attribute-fidelity 0
682 | ATTR integer job-k-octets 1024
683 | ATTR integer job-impressions 2048
684 | ATTR integer job-media-sheets 2
685 | GROUP job-attributes-tag
686 | ATTR integer job-priority 1
687 | ATTR keyword job-hold-until indefinite
688 | ATTR keyword job-sheets standard
689 | ATTR text auth-info "michael"
690 | ATTR text job-billing "no-idea"
691 | ATTR keyword multiple-document-handling single-document
692 | ATTR integer copies 2
693 | ATTR enum finishings punch
694 | ATTR rangeOfInteger page-ranges 1-6
695 | ATTR keyword sides two-sided-short-edge
696 | ATTR integer number-up 4
697 | ATTR enum orientation-requested reverse-landscape
698 | ATTR keyword media iso-a4-white
699 | ATTR resolution printer-resolution 600dpi
700 | ATTR enum print-quality 5
701 | }""").strip()
702 | assert request == expected_request, request
703 |
704 |
705 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
706 | def test_print_job(_call_ipptool):
707 | from pyipptool import print_job
708 | _call_ipptool.return_value = {'Tests': [{}]}
709 | filename = os.path.join(os.path.dirname(__file__), 'hello.pdf')
710 | with open(filename, 'rb') as tmp:
711 | print_job(printer_uri='ipp://cups:631/classes/p',
712 | job_name='foo',
713 | requesting_user_name='john-rambo',
714 | ipp_attribute_fidelity=False,
715 | document_name='foo.txt',
716 | compression='gzip',
717 | document_format='text/plain',
718 | document_natural_language='en',
719 | job_k_octets=1024,
720 | job_impressions=2048,
721 | job_media_sheets=2,
722 | job_priority=1,
723 | job_hold_until='indefinite',
724 | job_sheets='standard',
725 | auth_info='michael',
726 | job_billing='no-idea',
727 | multiple_document_handling='single-document',
728 | copies=2,
729 | finishings='punch',
730 | page_ranges='1-6',
731 | sides='two-sided-short-edge',
732 | number_up=4,
733 | orientation_requested='reverse-landscape',
734 | media='iso-a4-white',
735 | printer_resolution='600dpi',
736 | print_quality='5',
737 | document_content=tmp.read(),
738 | ezeep_job_uuid='bla')
739 | request = _call_ipptool._mock_mock_calls[0][1][-1]
740 | expected_request = textwrap.dedent("""
741 | {
742 | NAME "Print Job"
743 | OPERATION "Print-Job"
744 | GROUP operation-attributes-tag
745 | ATTR charset attributes-charset utf-8
746 | ATTR language attributes-natural-language en
747 | ATTR uri printer-uri ipp://cups:631/classes/p
748 | ATTR name requesting-user-name john-rambo
749 | ATTR name job-name foo
750 | ATTR boolean ipp-attribute-fidelity 0
751 | ATTR integer job-k-octets 1024
752 | ATTR integer job-impressions 2048
753 | ATTR integer job-media-sheets 2
754 | ATTR name document-name foo.txt
755 | ATTR keyword compression gzip
756 | ATTR mimeMediaType document-format text/plain
757 | ATTR naturalLanguage document-natural-language en
758 | GROUP job-attributes-tag
759 | ATTR integer job-priority 1
760 | ATTR keyword job-hold-until indefinite
761 | ATTR keyword job-sheets standard
762 | ATTR text auth-info "michael"
763 | ATTR text job-billing "no-idea"
764 | ATTR keyword multiple-document-handling single-document
765 | ATTR integer copies 2
766 | ATTR enum finishings punch
767 | ATTR rangeOfInteger page-ranges 1-6
768 | ATTR keyword sides two-sided-short-edge
769 | ATTR integer number-up 4
770 | ATTR enum orientation-requested reverse-landscape
771 | ATTR keyword media iso-a4-white
772 | ATTR resolution printer-resolution 600dpi
773 | ATTR enum print-quality 5
774 | ATTR text ezeep-job-uuid "bla"
775 | GROUP subscription-attributes-tag
776 | GROUP document-attributes-tag
777 | FILE /tmp/
778 | }""").strip()
779 | assert ('\n'.join(request.splitlines()[:-2])
780 | == '\n'.join(expected_request.splitlines()[:-2])), request
781 | assert expected_request.splitlines()[-2].startswith('FILE /tmp/')
782 |
783 |
784 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
785 | def test_send_document_with_file(_call_ipptool):
786 | from pyipptool import send_document
787 | _call_ipptool.return_value = {'Tests': [{}]}
788 |
789 | with tempfile.NamedTemporaryFile('rb') as tmp:
790 | send_document(printer_uri='ipp://cups:631/printers/p',
791 | requesting_user_name='you',
792 | document_content=tmp)
793 | request = _call_ipptool._mock_mock_calls[0][1][-1]
794 | expected_request = textwrap.dedent("""
795 | {
796 | NAME "Send Document"
797 | OPERATION "Send-Document"
798 | GROUP operation-attributes-tag
799 | ATTR charset attributes-charset utf-8
800 | ATTR language attributes-natural-language en
801 | ATTR uri printer-uri ipp://cups:631/printers/p
802 | ATTR name requesting-user-name you
803 | ATTR mimeMediaType document-format application/pdf
804 | ATTR boolean last-document 1
805 | GROUP document-attributes-tag
806 | FILE %s
807 | }""" % tmp.name).strip()
808 | assert request == expected_request
809 |
810 |
811 | @mock.patch.object(pyipptool.wrapper, '_call_ipptool')
812 | def test_send_document_with_binary(_call_ipptool):
813 | from pyipptool import send_document
814 | _call_ipptool.return_value = {'Tests': [{}]}
815 |
816 | with open(os.path.join(os.path.dirname(__file__),
817 | 'hello.pdf'), 'rb') as tmp:
818 | send_document(document_content=tmp.read())
819 | assert 'FILE /tmp/' in _call_ipptool._mock_mock_calls[0][1][-1]
820 |
--------------------------------------------------------------------------------
/tests/test_form.py:
--------------------------------------------------------------------------------
1 | import colander
2 | import mock
3 | import pytest
4 |
5 |
6 | def test_cancel_job_form():
7 | from pyipptool.forms import cancel_job_form
8 | request = cancel_job_form.render(
9 | {'operation_attributes_tag':
10 | {'printer_uri': 'https://localhost:631/classes/PIY',
11 | 'job_id': 8,
12 | 'job_uri': 'https://localhost:631/jobs/8',
13 | 'purge_job': True}})
14 | assert 'NAME "Cancel Job"' in request
15 | assert 'OPERATION "Cancel-Job"' in request
16 | assert 'ATTR uri printer-uri https://localhost:631/classes/PIY' in request
17 | assert 'ATTR integer job-id 8' in request
18 | assert 'ATTR uri job-uri https://localhost:631/jobs/8' in request
19 | assert 'ATTR boolean purge-job 1' in request
20 |
21 |
22 | def test_release_job_form_with_job_id():
23 | from pyipptool.forms import release_job_form
24 | request = release_job_form.render(
25 | {'operation_attributes_tag':
26 | {'printer_uri': 'https://localhost:631/classes/PIY',
27 | 'job_id': 7}})
28 | assert 'NAME "Release Job"' in request
29 | assert 'OPERATION "Release-Job"' in request
30 | assert 'ATTR uri printer-uri https://localhost:631/classes/PIY' in request
31 | assert 'ATTR integer job-id 7' in request
32 |
33 |
34 | def test_release_job_form_with_job_uri():
35 | from pyipptool.forms import release_job_form
36 | request = release_job_form.render(
37 | {'operation_attributes_tag':
38 | {'job_uri': 'https://localhost:631/jobs/7'}})
39 | assert 'NAME "Release Job"' in request
40 | assert 'OPERATION "Release-Job"' in request
41 | assert 'ATTR uri job-uri https://localhost:631/jobs/7' in request
42 |
43 |
44 | def test_create_printer_subscription_form():
45 | from pyipptool.forms import create_printer_subscription_form
46 | request = create_printer_subscription_form.render(
47 | {'operation_attributes_tag':
48 | {'printer_uri': 'https://localhost:631/classes/PIY',
49 | 'requesting_user_name': 'admin'},
50 | 'subscription_attributes_tag':
51 | {'notify_recipient_uri': 'rss://',
52 | 'notify_events': 'all',
53 | 'notify_attributes': 'notify-subscriber-user-name',
54 | 'notify_charset': 'utf-8',
55 | 'notify_natural_language': 'de',
56 | 'notify_lease_duration': 128,
57 | 'notify_time_interval': 1}})
58 | assert 'NAME "Create Printer Subscription"' in request, request
59 | assert 'OPERATION "Create-Printer-Subscription"' in request, request
60 | assert 'ATTR charset attributes-charset utf-8' in request, request
61 | assert 'ATTR language attributes-natural-language en' in request, request
62 | assert 'ATTR name requesting-user-name admin' in request, request
63 | assert 'GROUP subscription-attributes-tag' in request
64 | assert 'ATTR uri printer-uri https://localhost:631/classes/PIY' in request
65 | assert 'ATTR uri notify-recipient-uri rss://' in request
66 | assert 'ATTR keyword notify-events all' in request
67 | assert 'ATTR charset notify-charset utf-8' in request
68 | assert 'ATTR language notify-natural-language de' in request
69 | assert 'ATTR integer notify-lease-duration 128' in request
70 | assert 'ATTR integer notify-time-interval 1' in request
71 |
72 |
73 | def test_create_job_subscription_form_for_pull_delivery_method():
74 | from pyipptool.forms import create_job_subscription_form
75 | request = create_job_subscription_form.render(
76 | {'operation_attributes_tag':
77 | {'printer_uri': 'https://localhost:631/printer/p',
78 | 'requesting_user_name': 'admin'},
79 | 'subscription_attributes_tag':
80 | {'notify_recipient_uri': 'rss://',
81 | 'notify_job_id': 12,
82 | 'notify_events': ('job-completed', 'job-created', 'job-progress'),
83 | 'notify_attributes': 'notify-subscriber-user-name',
84 | 'notify_charset': 'utf-8',
85 | 'notify_natural_language': 'de',
86 | 'notify_time_interval': 1}})
87 | assert 'NAME "Create Job Subscription"' in request
88 | assert 'OPERATION "Create-Job-Subscription"' in request
89 |
90 | assert 'GROUP operation-attributes-tag' in request, request
91 | assert 'ATTR charset attributes-charset utf-8' in request
92 | assert 'ATTR language attributes-natural-language en' in request
93 | assert 'ATTR name requesting-user-name admin' in request
94 |
95 | assert 'GROUP subscription-attributes-tag' in request
96 | assert 'ATTR uri printer-uri https://localhost:631/printer/p' in request
97 | assert 'ATTR integer notify-job-id 12' in request, request
98 | assert 'ATTR uri notify-recipient-uri rss://' in request
99 | assert ('ATTR keyword notify-events job-completed,job-created,job-progress'
100 | in request), request
101 | assert 'ATTR charset notify-charset utf-8' in request
102 | assert 'ATTR language notify-natural-language de' in request
103 | assert 'ATTR integer notify-time-interval 1' in request
104 |
105 |
106 | def test_cups_add_modify_class_form():
107 | from pyipptool.forms import cups_add_modify_class_form
108 | m_uri_0 = 'ipp://localhost:631/printers/p0'
109 | m_uri_1 = 'ipp://localhost:631/classes/c0'
110 | request = cups_add_modify_class_form.render(
111 | {'operation_attributes_tag':
112 | {'printer_uri': 'https://localhost:631/printers/p0'},
113 | 'printer_attributes_tag':
114 | {'auth_info_required': 'john',
115 | 'member_uris': (m_uri_0, m_uri_1),
116 | 'printer_is_accepting_jobs': True,
117 | 'printer_info': 'multiline\ntext',
118 | 'printer_location': 'The Office',
119 | 'printer_more_info': 'http://example.com',
120 | 'printer_op_policy': 'brain',
121 | 'printer_state': '3',
122 | 'printer_state_message': 'Ready to print',
123 | 'requesting_user_name_allowed': 'me',
124 | 'printer_is_shared': False}})
125 | assert 'NAME "CUPS Add Modify Class"'
126 | assert 'OPERATION "CUPS-Add-Modify-Class"' in request
127 | assert 'GROUP operation-attributes-tag' in request, request
128 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
129 |
130 | assert 'GROUP printer-attributes-tag' in request
131 | assert 'ATTR uri member-uris %s,%s' % (m_uri_0, m_uri_1) in request
132 | assert 'ATTR keyword auth-info-required john' in request, request
133 | assert 'ATTR boolean printer-is-accepting-jobs 1' in request, request
134 | assert 'ATTR text printer-info "multiline\ntext"' in request
135 | assert 'ATTR text printer-location "The Office"' in request
136 | assert 'ATTR uri printer-more-info http://example.com' in request
137 | assert 'ATTR name printer-op-policy brain' in request
138 | assert 'ATTR enum printer-state 3' in request, request
139 | assert 'ATTR text printer-state-message "Ready to print"' in request
140 | assert 'ATTR name requesting-user-name-allowed me' in request
141 | assert 'ATTR boolean printer-is-shared 0' in request
142 |
143 |
144 | def test_cups_add_modify_printer_form():
145 | from pyipptool.forms import cups_add_modify_printer_form
146 | request = cups_add_modify_printer_form.render(
147 | {'operation_attributes_tag':
148 | {'printer_uri': 'https://localhost:631/printers/p0'},
149 | 'printer_attributes_tag':
150 | {'device_uri': 'cups-pdf:/',
151 | 'auth_info_required': 'john',
152 | 'job_sheets_default': 'none',
153 | 'port_monitor': 'port',
154 | 'ppd_name': 'printer.ppd',
155 | 'printer_is_accepting_jobs': True,
156 | 'printer_info': 'multiline\ntext',
157 | 'printer_location': 'The Office',
158 | 'printer_more_info': 'http://example.com',
159 | 'printer_op_policy': 'pinky',
160 | 'printer_state': '3',
161 | 'printer_state_message': 'Ready to print',
162 | 'requesting_user_name_allowed': 'me',
163 | 'printer_is_shared': True}})
164 | assert 'NAME "CUPS Add Modify Printer"'
165 | assert 'OPERATION "CUPS-Add-Modify-Printer"' in request
166 | assert 'GROUP operation-attributes-tag' in request
167 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
168 |
169 | assert 'GROUP printer-attributes-tag' in request
170 | assert 'ATTR uri device-uri cups-pdf:/' in request
171 | assert 'ATTR keyword auth-info-required john' in request
172 | assert 'ATTR name job-sheets-default none' in request
173 | assert 'ATTR name port-monitor port' in request
174 | assert 'ATTR name ppd-name printer.ppd' in request
175 | assert 'ATTR boolean printer-is-accepting-jobs 1' in request, request
176 | assert 'ATTR text printer-info "multiline\ntext"' in request
177 | assert 'ATTR text printer-location "The Office"' in request
178 | assert 'ATTR uri printer-more-info http://example.com' in request
179 | assert 'ATTR name printer-op-policy pinky' in request
180 | assert 'ATTR enum printer-state 3' in request
181 | assert 'ATTR text printer-state-message "Ready to print"' in request
182 | assert 'ATTR name requesting-user-name-allowed me' in request
183 | assert 'ATTR boolean printer-is-shared 1' in request
184 |
185 |
186 | def test_cups_add_modify_printer_form_with_None():
187 | from pyipptool.forms import cups_add_modify_printer_form
188 | with pytest.raises(ValueError) as exec_info:
189 | cups_add_modify_printer_form.render(
190 | {'operation_attributes_tag':
191 | {'printer_uri': 'https://localhost:631/printers/p0'},
192 | 'printer_attributes_tag': {'printer_state_message': None}})
193 | assert str(exec_info.value) == ("None value provided for"
194 | " 'printer_state_message'")
195 |
196 |
197 | def test_cups_delete_printer_form():
198 | from pyipptool.forms import cups_delete_printer_form
199 | request = cups_delete_printer_form.render(
200 | {'operation_attributes_tag':
201 | {'printer_uri': 'https://localhost:631/printers/p0'}})
202 | assert 'NAME "CUPS Delete Printer"' in request
203 | assert 'OPERATION "CUPS-Delete-Printer"' in request
204 | assert 'GROUP operation-attributes-tag' in request
205 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
206 |
207 |
208 | def test_cups_delete_class_form():
209 | from pyipptool.forms import cups_delete_class_form
210 | request = cups_delete_class_form.render(
211 | {'operation_attributes_tag':
212 | {'printer_uri': 'https://localhost:631/classes/p0'}})
213 | assert 'NAME "CUPS Delete Class"' in request
214 | assert 'OPERATION "CUPS-Delete-Class"' in request
215 | assert 'GROUP operation-attributes-tag' in request
216 | assert 'ATTR uri printer-uri https://localhost:631/classes/p0' in request
217 |
218 |
219 | def test_cups_get_classes_form():
220 | from pyipptool.forms import cups_get_classes_form
221 | request = cups_get_classes_form.render(
222 | {'operation_attributes_tag':
223 | {'first_printer_name': 'DA-Printer',
224 | 'limit': 2,
225 | 'printer_location': 'The Office',
226 | 'printer_type': '2',
227 | 'printer_type_mask': '8',
228 | 'requested_attributes': ('name', 'printer-attributes-tag'),
229 | 'requested_user_name': 'john'}})
230 | assert 'NAME "CUPS Get Classes"' in request, request
231 | assert 'OPERATION "CUPS-Get-Classes"' in request, request
232 | assert 'ATTR name first-printer-name DA-Printer' in request, request
233 | assert 'ATTR integer limit 2' in request
234 | assert 'ATTR text printer-location "The Office"' in request, request
235 | assert 'ATTR enum printer-type 2' in request
236 | assert 'ATTR enum printer-type-mask 8' in request
237 | assert ('ATTR keyword requested-attributes'
238 | ' name,printer-attributes-tag' in request), request
239 | assert 'ATTR name requested-user-name john' in request
240 |
241 |
242 | def test_cups_get_devices_form():
243 | from pyipptool.forms import cups_get_devices_form
244 | request = cups_get_devices_form.render(
245 | {'operation_attributes_tag':
246 | {'device_class': 'fermionic',
247 | 'exclude_schemes': 'foo',
248 | 'include_schemes': 'bar',
249 | 'limit': 3,
250 | 'requested_attributes': 'all',
251 | 'timeout': 12}})
252 | assert 'NAME "CUPS Get Devices"' in request
253 | assert 'OPERATION "CUPS-Get-Devices"' in request
254 | assert 'ATTR keyword device-class fermionic' in request
255 | assert 'ATTR name exclude-schemes foo' in request
256 | assert 'ATTR name include-schemes bar' in request
257 | assert 'ATTR integer limit 3' in request
258 | assert 'ATTR keyword requested-attributes all' in request
259 | assert 'ATTR integer timeout 12' in request
260 |
261 |
262 | def test_cups_get_ppds_form():
263 | from pyipptool.forms import cups_get_ppds_form
264 | request = cups_get_ppds_form.render(
265 | {'operation_attributes_tag':
266 | {'exclude_schemes': 'foo',
267 | 'include_schemes': 'bar',
268 | 'limit': 3,
269 | 'ppd_make': 'Manufaktur',
270 | 'ppd_make_and_model': 'Manufaktur XYZ',
271 | 'ppd_model_number': '1234',
272 | 'ppd_natural_language': 'en',
273 | 'ppd_product': 'Generic',
274 | 'ppd_psversion': 'PS3',
275 | 'ppd_type': 'generic',
276 | 'requested_attributes': 'all'}})
277 | assert 'NAME "CUPS Get PPDs"' in request
278 | assert 'OPERATION "CUPS-Get-PPDs"' in request
279 | assert 'ATTR name exclude-schemes foo' in request
280 | assert 'ATTR name include-schemes bar' in request
281 | assert 'ATTR text ppd-make "Manufaktur"' in request
282 | assert 'ATTR text ppd-make-and-model "Manufaktur XYZ"' in request
283 | assert 'ATTR integer ppd-model-number 1234' in request
284 | assert 'ATTR naturalLanguage ppd-natural-language en' in request
285 | assert 'ATTR text ppd-product "Generic"' in request
286 | assert 'ATTR text ppd-psversion "PS3"' in request
287 | assert 'ATTR keyword ppd-type generic' in request
288 | assert 'ATTR keyword requested-attributes all' in request
289 | assert 'ATTR integer limit 3' in request
290 |
291 |
292 | def test_cups_get_printers_form():
293 | from pyipptool.forms import cups_get_printers_form
294 | request = cups_get_printers_form.render(
295 | {'operation_attributes_tag':
296 | {'first_printer_name': 'DA-Printer',
297 | 'limit': 2,
298 | 'printer_location': 'The Office',
299 | 'printer_type': '2',
300 | 'printer_type_mask': '8',
301 | 'requested_attributes': ('name', 'printer-attributes-tag'),
302 | 'requested_user_name': 'john'}})
303 | assert 'NAME "CUPS Get Printers"' in request, request
304 | assert 'OPERATION "CUPS-Get-Printers"' in request, request
305 | assert 'ATTR name first-printer-name DA-Printer' in request, request
306 | assert 'ATTR integer limit 2' in request
307 | assert 'ATTR text printer-location "The Office"' in request, request
308 | assert 'ATTR enum printer-type 2' in request
309 | assert 'ATTR enum printer-type-mask 8' in request
310 | assert ('ATTR keyword requested-attributes'
311 | ' name,printer-attributes-tag' in request), request
312 | assert 'ATTR name requested-user-name john' in request
313 |
314 |
315 | def test_cups_reject_jobs_form():
316 | from pyipptool.forms import cups_reject_jobs_form
317 | request = cups_reject_jobs_form.render(
318 | {'operation_attributes_tag':
319 | {'printer_uri': 'ipp://cups:631/printers/p',
320 | 'requesting_user_name': 'admin'},
321 | 'printer_attributes_tag':
322 | {'printer_state_message': 'You shall not pass'}})
323 | assert 'NAME "CUPS Reject Jobs"' in request, request
324 | assert 'OPERATION "CUPS-Reject-Jobs"' in request, request
325 |
326 | assert 'GROUP operation-attributes-tag' in request
327 | assert 'ATTR uri printer-uri ipp://cups:631/printers/p' in request
328 |
329 | assert 'GROUP printer-attributes-tag' in request
330 | assert 'ATTR text printer-state-message "You shall not pass"' in request
331 |
332 |
333 | def test_get_job_attributes_form():
334 | from pyipptool.forms import get_job_attributes_form
335 | request = get_job_attributes_form.render(
336 | {'operation_attributes_tag':
337 | {'printer_uri':
338 | 'https://localhost:631/printers/DA-PRINTER',
339 | 'job_id': 2,
340 | 'requesting_user_name': 'susan',
341 | 'requested_attributes': 'job-uri'}})
342 | assert 'NAME "Get Job Attributes"' in request
343 | assert 'OPERATION "Get-Job-Attributes"' in request
344 | assert 'ATTR uri printer-uri https://localhost:631/printers/DA-PRINTER'\
345 | in request
346 | assert 'ATTR integer job-id 2' in request
347 | assert 'ATTR name requesting-user-name susan' in request
348 | assert 'ATTR keyword requested-attributes job-uri' in request
349 |
350 |
351 | def test_get_jobs_form():
352 | from pyipptool.forms import get_jobs_form
353 | request = get_jobs_form.render(
354 | {'operation_attributes_tag':
355 | {'printer_uri': 'https://localhost:631/printers/p0',
356 | 'requesting_user_name': 'yoda',
357 | 'limit': 1,
358 | 'requested_attributes': 'job-uri',
359 | 'which_jobs': 'pending',
360 | 'my_jobs': True}})
361 | assert 'NAME "Get Jobs"' in request
362 | assert 'OPERATION "Get-Jobs"' in request
363 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
364 | assert 'ATTR name requesting-user-name yoda' in request
365 | assert 'ATTR integer limit 1' in request
366 | assert 'ATTR keyword requested-attributes job-uri' in request
367 | assert 'ATTR keyword which-jobs pending' in request
368 | assert 'ATTR keyword requested-attributes job-uri' in request
369 |
370 |
371 | def test_get_printer_attributes_form():
372 | from pyipptool.forms import get_printer_attributes_form
373 | request = get_printer_attributes_form.render(
374 | {'operation_attributes_tag':
375 | {'printer_uri':
376 | 'https://localhost:631/printers/p0',
377 | 'requesting_user_name': 'yoda',
378 | 'requested_attributes': ('printer-name', 'operations-supported')}})
379 | assert 'NAME "Get Printer Attributes"' in request
380 | assert 'OPERATION "Get-Printer-Attributes"' in request
381 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
382 | assert 'ATTR name requesting-user-name yoda' in request
383 | assert 'ATTR keyword requested-attributes'\
384 | ' printer-name,operations-supported' in request
385 |
386 |
387 | def test_get_subscriptions_form():
388 | from pyipptool.forms import get_subscriptions_form
389 | request = get_subscriptions_form.render(
390 | {'operation_attributes_tag':
391 | {'printer_uri': 'https://localhost:631/printers/p0',
392 | 'requesting_user_name': 'yoda',
393 | 'notify_job_id': 3,
394 | 'limit': 1,
395 | 'requested_attributes': 'notify-recipient-uri',
396 | 'my_subscriptions': True}})
397 | assert 'NAME "Get Subscriptions"' in request
398 | assert 'OPERATION "Get-Subscriptions"' in request
399 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
400 | assert 'ATTR name requesting-user-name yoda' in request
401 | assert 'ATTR integer notify-job-id 3' in request
402 | assert 'ATTR integer limit 1' in request
403 | assert 'ATTR keyword requested-attributes notify-recipient-uri' in request
404 | assert 'ATTR boolean my-subscriptions 1' in request
405 |
406 |
407 | def test_get_notifications_form_for_one_notification():
408 | from pyipptool.forms import get_notifications_form
409 | request = get_notifications_form.render(
410 | {'operation_attributes_tag':
411 | {'printer_uri': 'https://localhost:631/printers/p0',
412 | 'requesting_user_name': 'yoda',
413 | 'notify_subscription_ids': 3,
414 | 'notify_sequence_numbers': 1,
415 | 'notify_wait': True}})
416 | assert 'NAME "Get Notifications"' in request
417 | assert 'OPERATION "Get-Notifications"' in request
418 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
419 | assert 'ATTR name requesting-user-name yoda' in request
420 | assert 'ATTR integer notify-subscription-ids 3' in request
421 | assert 'ATTR integer notify-sequence-numbers 1' in request
422 | assert 'ATTR boolean notify-wait 1' in request
423 |
424 |
425 | def test_get_notifications_form_for_multiple_notifications():
426 | from pyipptool.forms import get_notifications_form
427 | request = get_notifications_form.render(
428 | {'operation_attributes_tag':
429 | {'printer_uri': 'https://localhost:631/printers/p0',
430 | 'requesting_user_name': 'yoda',
431 | 'notify_subscription_ids': (3, 4, 5),
432 | 'notify_sequence_numbers': (2, 9, 29),
433 | 'notify_wait': True}})
434 | assert 'NAME "Get Notifications"' in request
435 | assert 'OPERATION "Get-Notifications"' in request
436 | assert 'ATTR uri printer-uri https://localhost:631/printers/p0' in request
437 | assert 'ATTR name requesting-user-name yoda' in request
438 | assert 'ATTR integer notify-subscription-ids 3,4,5' in request
439 | assert 'ATTR integer notify-sequence-numbers 2,9,29' in request
440 | assert 'ATTR boolean notify-wait 1' in request
441 |
442 |
443 | def test_pause_printer_form():
444 | from pyipptool.forms import pause_printer_form
445 | request = pause_printer_form.render(
446 | {'operation_attributes_tag':
447 | {'printer_uri': 'ipp://server:port/printers/name',
448 | 'requesting_user_name': 'yoda'}})
449 | assert 'NAME "Pause Printer"' in request
450 | assert 'OPERATION "Pause-Printer"' in request
451 | assert 'ATTR uri printer-uri ipp://server:port/printers/name' in request
452 | assert 'ATTR name requesting-user-name yoda' in request
453 |
454 |
455 | def test_resume_printer_form():
456 | from pyipptool.forms import resume_printer_form
457 | request = resume_printer_form.render(
458 | {'operation_attributes_tag':
459 | {'printer_uri': 'ipp://server:port/printers/name',
460 | 'requesting_user_name': 'yoda'}})
461 | assert 'NAME "Resume Printer"' in request
462 | assert 'OPERATION "Resume-Printer"' in request
463 | assert 'ATTR uri printer-uri ipp://server:port/printers/name' in request
464 | assert 'ATTR name requesting-user-name yoda' in request
465 |
466 |
467 | def test_hold_new_jobs_form():
468 | from pyipptool.forms import hold_new_jobs_form
469 | request = hold_new_jobs_form.render(
470 | {'operation_attributes_tag':
471 | {'printer_uri': 'ipp://server:port/printers/name',
472 | 'requesting_user_name': 'yoda',
473 | 'printer_message_from_operator': 'freeze jobs'}})
474 | assert 'NAME "Hold New Jobs"' in request
475 | assert 'OPERATION "Hold-New-Jobs"' in request
476 | assert 'ATTR uri printer-uri ipp://server:port/printers/name' in request
477 | assert 'ATTR name requesting-user-name yoda' in request
478 | assert 'ATTR text printer-message-from-operator "freeze jobs"' in request
479 |
480 |
481 | def test_release_held_new_jobs_form():
482 | from pyipptool.forms import release_held_new_jobs_form
483 | request = release_held_new_jobs_form.render(
484 | {'operation_attributes_tag':
485 | {'printer_uri': 'ipp://server:port/printers/name',
486 | 'requesting_user_name': 'yoda',
487 | 'printer_message_from_operator': 'melt jobs'}})
488 | assert 'NAME "Release Held New Jobs"' in request
489 | assert 'OPERATION "Release-Held-New-Jobs"' in request
490 | assert 'ATTR uri printer-uri ipp://server:port/printers/name' in request
491 | assert 'ATTR name requesting-user-name yoda' in request
492 | assert 'ATTR text printer-message-from-operator "melt jobs"' in request
493 |
494 |
495 | def test_cancel_subscription_form():
496 | from pyipptool.forms import cancel_subscription_form
497 | request = cancel_subscription_form.render(
498 | {'operation_attributes_tag':
499 | {'printer_uri': 'ipp://server:port/printers/name',
500 | 'requesting_user_name': 'yoda',
501 | 'notify_subscription_id': 5}})
502 | assert 'NAME "Cancel Subscription"' in request
503 | assert 'OPERATION "Cancel-Subscription"' in request
504 | assert 'ATTR uri printer-uri ipp://server:port/printers/name' in request
505 | assert 'ATTR name requesting-user-name yoda' in request
506 | assert 'ATTR integer notify-subscription-id 5' in request, request
507 |
508 |
509 | def test_create_job_form():
510 | """
511 | http://www.cups.org/documentation.php/spec-ipp.html#CREATE_JOB
512 | """
513 | from pyipptool.forms import create_job_form
514 | request = create_job_form.render(
515 | {'operation_attributes_tag':
516 | {'printer_uri': 'ipp://server:port/printers/name',
517 | 'job_name': 'foo',
518 | 'ipp_attribute_fidelity': True,
519 | 'job_k_octets': 1024,
520 | 'job_impressions': 2048,
521 | 'job_media_sheets': 2},
522 | 'job_attributes_tag':
523 | {'job_priority': 1,
524 | 'job_hold_until': 'indefinite',
525 | 'job_sheets': 'standard',
526 | 'media': 'iso-a4-white',
527 | 'auth_info': 'michael',
528 | 'job_billing': 'no-idea',
529 | 'multiple_document_handling': 'single-document',
530 | 'copies': 2,
531 | 'finishings': 'punch',
532 | 'page_ranges': '1-6',
533 | 'sides': 'two-sided-short-edge',
534 | 'number_up': 4,
535 | 'orientation_requested': 'reverse-landscape',
536 | 'printer_resolution': '600dpi',
537 | 'print_quality': 5}})
538 | assert 'NAME "Create Job"' in request
539 | assert 'OPERATION "Create-Job"' in request
540 | assert ('ATTR uri printer-uri ipp://server:port/printers/name' in
541 | request), request
542 | assert 'ATTR name job-name foo' in request
543 | assert 'ATTR boolean ipp-attribute-fidelity 1' in request, request
544 | assert 'ATTR integer job-k-octets 1024' in request
545 | assert 'ATTR integer job-impressions 2048' in request
546 | assert 'ATTR integer job-media-sheets 2' in request
547 | assert 'ATTR text auth-info "michael"' in request, request
548 | assert 'ATTR text job-billing "no-idea"' in request, request
549 |
550 | assert 'GROUP job-attributes-tag' in request
551 | assert 'ATTR integer job-priority 1' in request
552 | assert 'ATTR keyword job-hold-until indefinite' in request
553 | assert 'ATTR keyword job-sheets standard' in request
554 | assert 'ATTR keyword multiple-document-handling single-document' in request
555 | assert 'ATTR integer copies 2' in request
556 | assert 'ATTR enum finishings punch' in request
557 | assert 'ATTR rangeOfInteger page-ranges 1-6' in request
558 | assert 'ATTR keyword sides two-sided-short-edge' in request
559 | assert 'ATTR integer number-up 4' in request
560 | assert 'ATTR enum orientation-requested reverse-landscape' in request
561 | assert 'ATTR keyword media iso-a4-white' in request
562 | assert 'ATTR resolution printer-resolution 600dpi' in request
563 | assert 'ATTR enum print-quality 5' in request
564 |
565 |
566 | def test_print_job_form():
567 | from pyipptool.forms import print_job_form
568 | request = print_job_form.render(
569 | {'operation_attributes_tag':
570 | {'printer_uri': 'ipp://server:port/printers/name',
571 | 'job_name': 'foo',
572 | 'ipp_attribute_fidelity': True,
573 | 'document_name': 'foo.txt',
574 | 'compression': 'gzip',
575 | 'document_format': 'text/plain',
576 | 'document_natural_language': 'en',
577 | 'job_k_octets': 1024,
578 | 'job_impressions': 2048,
579 | 'job_media_sheets': 2},
580 | 'job_attributes_tag':
581 | {'job_priority': 1,
582 | 'job_hold_until': 'indefinite',
583 | 'job_sheets': 'standard',
584 | 'auth_info': 'michael',
585 | 'job_billing': 'no-idea',
586 | 'media': 'media-default',
587 | 'multiple_document_handling': 'single-document',
588 | 'copies': 2,
589 | 'finishings': 'punch',
590 | 'page_ranges': '1-6',
591 | 'sides': 'two-sided-short-edge',
592 | 'number_up': 4,
593 | 'orientation_requested': 'reverse-landscape',
594 | 'printer_resolution': '600dpi',
595 | 'print_quality': 5},
596 | 'subscription_attributes_tag':
597 | {'notify_recipient_uri': 'rss://',
598 | 'notify_events': ['all']},
599 | 'document_attributes_tag':
600 | {'file': '/path/to/file.txt'}})
601 |
602 | assert 'NAME "Print Job"' in request
603 | assert 'OPERATION "Print-Job"' in request
604 | assert ('ATTR uri printer-uri ipp://server:port/printers/name' in
605 | request), request
606 | assert 'GROUP operation-attributes-tag' in request
607 | assert 'ATTR name job-name foo' in request
608 | assert 'ATTR boolean ipp-attribute-fidelity 1' in request, request
609 | assert 'ATTR name document-name foo.txt' in request
610 | assert 'ATTR keyword compression gzip' in request
611 | assert 'ATTR mimeMediaType document-format text/plain' in request
612 | assert 'ATTR naturalLanguage document-natural-language en' in request
613 | assert 'ATTR integer job-k-octets 1024' in request
614 | assert 'ATTR integer job-impressions 2048' in request
615 | assert 'ATTR integer job-media-sheets 2' in request
616 |
617 | assert 'GROUP job-attributes-tag' in request
618 | assert 'ATTR integer job-priority 1' in request
619 | assert 'ATTR keyword job-hold-until indefinite' in request
620 | assert 'ATTR keyword job-sheets standard' in request
621 | assert 'ATTR text auth-info "michael"' in request, request
622 | assert 'ATTR text job-billing "no-idea"' in request, request
623 | assert 'ATTR keyword job-sheets standard' in request, request
624 | assert 'ATTR keyword media media-default' in request
625 | assert 'ATTR keyword multiple-document-handling single-document' in request
626 | assert 'ATTR integer copies 2' in request
627 | assert 'ATTR enum finishings punch' in request
628 | assert 'ATTR rangeOfInteger page-ranges 1-6' in request
629 | assert 'ATTR keyword sides two-sided-short-edge' in request
630 | assert 'ATTR integer number-up 4' in request
631 | assert 'ATTR enum orientation-requested reverse-landscape' in request
632 | assert 'ATTR resolution printer-resolution 600dpi' in request
633 | assert 'ATTR enum print-quality 5' in request
634 |
635 | assert 'GROUP subscription-attributes-tag' in request
636 | assert 'ATTR uri notify-recipient-uri rss://' in request
637 | assert 'ATTR keyword notify-events all' in request
638 |
639 | assert 'GROUP document-attributes-tag' in request
640 | assert 'FILE /path/to/file.txt' in request
641 |
642 | assert (request.index('GROUP operation-attributes-tag') <
643 | request.index('GROUP job-attributes-tag') <
644 | request.index('GROUP subscription-attributes-tag') <
645 | request.index('GROUP document-attributes-tag')
646 | )
647 |
648 |
649 | def test_send_document_form():
650 | from pyipptool.forms import send_document_form
651 |
652 | request = send_document_form.render(
653 | {'operation_attributes_tag':
654 | {'job_uri': 'http://cups:631/jobs/2',
655 | 'requesting_user_name': 'sweet',
656 | 'document_name': 'python.pdf',
657 | 'compression': 'gzip',
658 | 'document_format': 'application/pdf',
659 | 'document_natural_language': 'en',
660 | 'last_document': True},
661 | 'document_attributes_tag':
662 | {'file': '/path/to/a/file.pdf'}})
663 | assert 'NAME "Send Document"' in request
664 | assert 'OPERATION "Send-Document"' in request
665 |
666 | assert 'GROUP operation-attributes-tag' in request
667 | assert 'ATTR uri job-uri http://cups:631/jobs/2' in request
668 | assert 'ATTR name requesting-user-name sweet' in request
669 | assert 'ATTR name document-name python.pdf' in request
670 | assert 'ATTR keyword compression gzip' in request
671 | assert 'ATTR mimeMediaType document-format application/pdf' in request
672 | assert 'ATTR naturalLanguage document-natural-language en' in request
673 | assert 'ATTR boolean last-document 1' in request, request
674 |
675 | assert 'GROUP document-attributes-tag'
676 | assert 'FILE /path/to/a/file.pdf' in request, request
677 |
678 |
679 | def test_range_of_integer_validator():
680 | from pyipptool.schemas import range_of_integer_validator
681 | with pytest.raises(colander.Invalid):
682 | range_of_integer_validator(mock.MagicMock(), '12-98d')
683 |
684 | with pytest.raises(colander.Invalid):
685 | range_of_integer_validator(mock.MagicMock(), '-12')
686 |
687 | with pytest.raises(colander.Invalid):
688 | range_of_integer_validator(mock.MagicMock(), '10-9')
689 |
690 | range_of_integer_validator(mock.MagicMock(), '2-87')
691 |
--------------------------------------------------------------------------------
/pyipptool/core.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import logging
3 | import os
4 | import plistlib
5 | import shutil
6 | import subprocess
7 | import tempfile
8 | import time
9 | import threading
10 |
11 | from future import standard_library
12 | from future.builtins import bytes, str
13 | from future.utils import PY3
14 | with standard_library.hooks():
15 | import urllib.parse
16 |
17 | import colander
18 |
19 | from .forms import (cancel_job_form,
20 | release_job_form,
21 | create_job_form,
22 | create_job_subscription_form,
23 | create_printer_subscription_form,
24 | cups_add_modify_class_form,
25 | cups_add_modify_printer_form,
26 | cups_delete_printer_form,
27 | cups_delete_class_form,
28 | cups_get_classes_form,
29 | cups_get_devices_form,
30 | cups_get_ppd_form,
31 | cups_get_ppds_form,
32 | cups_get_printers_form,
33 | cups_move_job_form,
34 | cups_reject_jobs_form,
35 | get_job_attributes_form,
36 | get_jobs_form,
37 | get_printer_attributes_form,
38 | get_subscriptions_form,
39 | get_notifications_form,
40 | pause_printer_form,
41 | print_job_form,
42 | resume_printer_form,
43 | send_document_form,
44 | hold_new_jobs_form,
45 | release_held_new_jobs_form,
46 | cancel_subscription_form,
47 | )
48 |
49 | try:
50 | from tornado.gen import coroutine, Return, Task
51 | from tornado.ioloop import TimeoutError
52 | except ImportError:
53 | def coroutine(f):
54 | return f
55 |
56 | class TimeoutError(Exception):
57 | pass
58 |
59 | class Return(Exception):
60 | def __init__(self, value):
61 | self.value = value
62 |
63 |
64 | def pyipptool_coroutine(method):
65 | """
66 | Mark the method as a coroutine.
67 | If use with tornado the side effect of this decorator will be
68 | cancelled and the original_method will be wrapped by
69 | tornado.gen.coroutine .
70 | Otherwise the sync_coroutine_consumer wrapper
71 | will take care to consume the generator synchronously.
72 | """
73 | method.ipptool_caller = True
74 |
75 | @functools.wraps(method)
76 | def sync_coroutine_consumer(*args, **kw):
77 | gen = method(*args, **kw)
78 | while True:
79 | value = next(gen)
80 | try:
81 | gen.send(value)
82 | except Return as returned:
83 | return returned.value
84 | except StopIteration:
85 | return value
86 | sync_coroutine_consumer.original_method = method
87 | return sync_coroutine_consumer
88 |
89 |
90 | def _get_filename_for_content(content):
91 | """
92 | Return the name of a file based on type of content
93 | - already a file ?
94 | - does he have a name ?
95 | take its name
96 | - else
97 | copy to temp file and return its name
98 | - binary content ?
99 | copy to temp file and return its name
100 |
101 | if a temp file is created the caller is responsible to
102 | destroy the file. the flag delete is meant for it.
103 | """
104 | file_ = None
105 | delete = False
106 | if content is colander.null:
107 | return content, delete
108 | if hasattr(getattr(content, 'file', None), 'read'):
109 | # tempfile
110 | file_ = content
111 | if hasattr(content, 'read'):
112 | # most likely a file like object
113 | file_ = content
114 | if file_ is not None:
115 | if file_.name:
116 | name = file_.name
117 | else:
118 | with tempfile.NamedTemporaryFile(delete=False,
119 | mode='rb') as tmp:
120 | delete = True
121 | shutil.copyfileobj(file_, tmp)
122 | name = tmp.name
123 | elif isinstance(content, (str, bytes)):
124 | with tempfile.NamedTemporaryFile(delete=False) as tmp:
125 | delete = True
126 | tmp.write(content)
127 | name = tmp.name
128 | else:
129 | raise NotImplementedError(
130 | 'Got unknow document\'s content type {}'.format(
131 | type(content)))
132 |
133 | return name, delete
134 |
135 |
136 | def pretty_printer(form):
137 | """
138 | Remove blank lines
139 | """
140 | return '\n'.join((line.strip() for line in form.splitlines()
141 | if line and not line.isspace()))
142 |
143 |
144 | class MetaAsyncShifter(type):
145 | """
146 | Based on async flage defined on IPPToolWrapper
147 | methods will be decorated by tornado.gen.coroutine otherwise
148 | with a fake one.
149 | """
150 | def __new__(cls, name, bases, attrs):
151 | klass = super(MetaAsyncShifter, cls).__new__(cls, name, bases, attrs)
152 | if attrs.get('async'):
153 | # ASYNC Wrapper
154 | for method_name in dir(bases[0]):
155 | method = getattr(bases[0], method_name)
156 | if getattr(method, 'ipptool_caller', False):
157 | # Patch Method with tornado.gen.coroutine
158 | setattr(klass, method_name,
159 | coroutine(method.original_method))
160 | return klass
161 |
162 |
163 | class IPPToolWrapper(object):
164 | __metaclass__ = MetaAsyncShifter
165 | async = False
166 |
167 | def __init__(self, config):
168 | self.config = config
169 |
170 | @property
171 | def authenticated_uri(self):
172 | if 'login' in self.config and 'password' in self.config:
173 | parsed_url = urllib.parse.urlparse(self.config['cups_uri'])
174 | authenticated_netloc = '{}:{}@{}'.format(self.config['login'],
175 | self.config['password'],
176 | parsed_url.netloc)
177 | authenticated_uri = urllib.parse.ParseResult(parsed_url[0],
178 | authenticated_netloc,
179 | *parsed_url[2:])
180 | return authenticated_uri.geturl()
181 | return self.config['cups_uri']
182 |
183 | def timeout_handler(self, process, future):
184 | future.append(True)
185 | beginning = time.time()
186 | process.terminate()
187 | while process.poll() is None:
188 | if time.time() - beginning > self.config['graceful_shutdown_time']:
189 | try:
190 | process.kill()
191 | except OSError:
192 | pass
193 | break
194 | time.sleep(.1)
195 |
196 | def _call_ipptool(self, request):
197 | with tempfile.NamedTemporaryFile(delete=False) as temp_file:
198 | temp_file.write(bytes(request, encoding='utf-8'))
199 | process = subprocess.Popen([self.config['ipptool_path'],
200 | self.authenticated_uri,
201 | '-X',
202 | temp_file.name],
203 | stdin=subprocess.PIPE,
204 | stdout=subprocess.PIPE,
205 | stderr=subprocess.PIPE)
206 | future = []
207 | timer = threading.Timer(self.config['timeout'],
208 | self.timeout_handler, (process, future))
209 | timer.start()
210 | try:
211 | stdout, stderr = process.communicate()
212 | finally:
213 | os.unlink(temp_file.name)
214 | timer.cancel()
215 | if future:
216 | raise TimeoutError
217 | if PY3:
218 | result = plistlib.loads(stdout)
219 | else:
220 | result = plistlib.readPlistFromString(stdout)
221 | try:
222 | return result['Tests'][0]
223 | except (IndexError, KeyError):
224 | logger = logging.getLogger(__name__)
225 | logger.error('ipptool command failed: {} {}'.format(stdout,
226 | stderr))
227 | raise
228 |
229 | @pyipptool_coroutine
230 | def release_job(self,
231 | printer_uri=colander.null,
232 | job_id=colander.null,
233 | job_uri=colander.null):
234 | kw = {'operation_attributes_tag':
235 | {'printer_uri': printer_uri,
236 | 'job_id': job_id,
237 | 'job_uri': job_uri}}
238 | request = pretty_printer(release_job_form.render(kw))
239 | response = yield self._call_ipptool(request)
240 | raise Return(response)
241 |
242 | @pyipptool_coroutine
243 | def cancel_job(self,
244 | printer_uri=colander.null,
245 | job_id=colander.null,
246 | job_uri=colander.null,
247 | purge_job=colander.null):
248 | kw = {'operation_attributes_tag':
249 | {'printer_uri': printer_uri,
250 | 'job_id': job_id,
251 | 'job_uri': job_uri,
252 | 'purge_job': purge_job}}
253 | request = pretty_printer(cancel_job_form.render(kw))
254 | response = yield self._call_ipptool(request)
255 | raise Return(response)
256 |
257 | @pyipptool_coroutine
258 | def create_job(self,
259 | printer_uri=None,
260 | job_name=colander.null,
261 | ipp_attribute_fidelity=colander.null,
262 | job_k_octets=colander.null,
263 | job_impressions=colander.null,
264 | job_media_sheets=colander.null,
265 | job_priority=colander.null,
266 | job_hold_until=colander.null,
267 | multiple_document_handling=colander.null,
268 | copies=colander.null,
269 | finishings=colander.null,
270 | page_ranges=colander.null,
271 | sides=colander.null,
272 | number_up=colander.null,
273 | orientation_requested=colander.null,
274 | printer_resolution=colander.null,
275 | print_quality=colander.null,
276 | auth_info=colander.null,
277 | job_billing=colander.null,
278 | job_sheets=colander.null,
279 | media=colander.null):
280 | kw = {'operation_attributes_tag':
281 | {'printer_uri': printer_uri,
282 | 'job_name': job_name,
283 | 'ipp_attribute_fidelity': ipp_attribute_fidelity,
284 | 'job_k_octets': job_k_octets,
285 | 'job_impressions': job_impressions,
286 | 'job_media_sheets': job_media_sheets},
287 | 'job_attributes_tag':
288 | {'job_priority': job_priority,
289 | 'job_hold_until': job_hold_until,
290 | 'job_sheets': job_sheets,
291 | 'multiple_document_handling': multiple_document_handling,
292 | 'copies': copies,
293 | 'finishings': finishings,
294 | 'page_ranges': page_ranges,
295 | 'sides': sides,
296 | 'number_up': number_up,
297 | 'orientation_requested': orientation_requested,
298 | 'media': media,
299 | 'printer_resolution': printer_resolution,
300 | 'print_quality': print_quality,
301 | 'auth_info': auth_info,
302 | 'job_billing': job_billing,
303 | 'job_sheets': job_sheets,
304 | 'media': media}}
305 | request = pretty_printer(create_job_form.render(kw))
306 | response = yield self._call_ipptool(request)
307 | raise Return(response)
308 |
309 | @pyipptool_coroutine
310 | def print_job(self,
311 | printer_uri=None,
312 | requesting_user_name=colander.null,
313 | job_name=colander.null,
314 | ipp_attribute_fidelity=colander.null,
315 | document_name=colander.null,
316 | compression=colander.null,
317 | document_format=colander.null,
318 | document_natural_language=colander.null,
319 | job_k_octets=colander.null,
320 | job_impressions=colander.null,
321 | job_media_sheets=colander.null,
322 | job_priority=colander.null,
323 | job_hold_until=colander.null,
324 | multiple_document_handling=colander.null,
325 | copies=colander.null,
326 | finishings=colander.null,
327 | page_ranges=colander.null,
328 | sides=colander.null,
329 | number_up=colander.null,
330 | orientation_requested=colander.null,
331 | printer_resolution=colander.null,
332 | print_quality=colander.null,
333 | ezeep_job_uuid=colander.null,
334 | notify_recipient_uri=colander.null,
335 | notify_events=colander.null,
336 | notify_time_interval=colander.null,
337 | auth_info=colander.null,
338 | job_billing=colander.null,
339 | job_sheets=colander.null,
340 | media=colander.null,
341 | document_content=None,
342 | ):
343 | filename, delete = _get_filename_for_content(document_content)
344 | kw = {'operation_attributes_tag':
345 | {'printer_uri': printer_uri,
346 | 'requesting_user_name': requesting_user_name,
347 | 'job_name': job_name,
348 | 'ipp_attribute_fidelity': ipp_attribute_fidelity,
349 | 'document_name': document_name,
350 | 'compression': compression,
351 | 'document_format': document_format,
352 | 'document_natural_language': document_natural_language,
353 | 'job_k_octets': job_k_octets,
354 | 'job_impressions': job_impressions,
355 | 'job_media_sheets': job_media_sheets},
356 | 'job_attributes_tag':
357 | {'job_priority': job_priority,
358 | 'job_hold_until': job_hold_until,
359 | 'job_sheets': job_sheets,
360 | 'auth_info': auth_info,
361 | 'job_billing': job_billing,
362 | 'multiple_document_handling': multiple_document_handling,
363 | 'copies': copies,
364 | 'finishings': finishings,
365 | 'page_ranges': page_ranges,
366 | 'sides': sides,
367 | 'number_up': number_up,
368 | 'orientation_requested': orientation_requested,
369 | 'media': media,
370 | 'printer_resolution': printer_resolution,
371 | 'print_quality': print_quality,
372 | 'ezeep_job_uuid': ezeep_job_uuid,
373 | },
374 | 'subscription_attributes_tag':
375 | {'notify_recipient_uri': notify_recipient_uri,
376 | 'notify_events': notify_events,
377 | 'notify_time_interval': notify_time_interval},
378 | 'document_attributes_tag':
379 | {'file': filename}}
380 | request = pretty_printer(print_job_form.render(kw))
381 | try:
382 | response = yield self._call_ipptool(request)
383 | raise Return(response)
384 | finally:
385 | if delete:
386 | os.unlink(filename)
387 |
388 | @pyipptool_coroutine
389 | def create_job_subscription(self,
390 | requesting_user_name=None,
391 | printer_uri=colander.null,
392 | job_id=colander.null,
393 | job_uri=colander.null,
394 | notify_job_id=colander.null,
395 | notify_recipient_uri=colander.null,
396 | notify_pull_method=colander.null,
397 | notify_events=colander.null,
398 | notify_attributes=colander.null,
399 | notify_charset=colander.null,
400 | notify_natural_language=colander.null,
401 | notify_time_interval=colander.null):
402 | """
403 | Create a per-job subscription object.
404 |
405 |
406 | Create-Job-Subscriptions
407 | https://tools.ietf.org/html/rfc3995#section-11.1.1
408 |
409 | https://www.cups.org/str.php?L4389
410 | """
411 | kw = {'operation_attributes_tag':
412 | {'printer_uri': printer_uri,
413 | 'requesting_user_name': requesting_user_name,
414 | 'job_id': job_id,
415 | 'job_uri': job_uri},
416 | 'subscription_attributes_tag':
417 | {'notify_job_id': notify_job_id,
418 | 'notify_recipient_uri': notify_recipient_uri,
419 | 'notify_pull_method': notify_pull_method,
420 | 'notify_events': notify_events,
421 | 'notify_attributes': notify_attributes,
422 | 'notify_charset': notify_charset,
423 | 'notify_natural_language': notify_natural_language,
424 | 'notify_time_interval': notify_time_interval}}
425 | request = pretty_printer(create_job_subscription_form.render(kw))
426 | response = yield self._call_ipptool(request)
427 | raise Return(response)
428 |
429 | @pyipptool_coroutine
430 | def create_printer_subscription(
431 | self,
432 | printer_uri=None,
433 | requesting_user_name=None,
434 | notify_recipient_uri=colander.null,
435 | notify_pull_method=colander.null,
436 | notify_events=colander.null,
437 | notify_attributes=colander.null,
438 | notify_charset=colander.null,
439 | notify_natural_language=colander.null,
440 | notify_lease_duration=colander.null,
441 | notify_time_interval=colander.null):
442 | """
443 | Create a new subscription and return its id
444 | """
445 | kw = {'operation_attributes_tag':
446 | {'printer_uri': printer_uri,
447 | 'requesting_user_name': requesting_user_name},
448 | 'subscription_attributes_tag':
449 | {'notify_recipient_uri': notify_recipient_uri,
450 | 'notify_pull_method': notify_pull_method,
451 | 'notify_events': notify_events,
452 | 'notify_attributes': notify_attributes,
453 | 'notify_charset': notify_charset,
454 | 'notify_natural_language': notify_natural_language,
455 | 'notify_lease_duration': notify_lease_duration,
456 | 'notify_time_interval': notify_time_interval}}
457 | request = pretty_printer(create_printer_subscription_form.render(kw))
458 | response = yield self._call_ipptool(request)
459 | raise Return(response)
460 |
461 | @pyipptool_coroutine
462 | def cups_add_modify_printer(self,
463 | printer_uri=None,
464 | auth_info_required=colander.null,
465 | job_sheets_default=colander.null,
466 | device_uri=colander.null,
467 | port_monitor=colander.null,
468 | ppd_name=colander.null,
469 | printer_is_accepting_jobs=colander.null,
470 | printer_info=colander.null,
471 | printer_location=colander.null,
472 | printer_more_info=colander.null,
473 | printer_op_policy=colander.null,
474 | printer_state=colander.null,
475 | printer_state_message=colander.null,
476 | requesting_user_name_allowed=colander.null,
477 | requesting_user_name_denied=colander.null,
478 | printer_is_shared=colander.null,
479 | ppd_content=colander.null,
480 | ):
481 | filename, delete = _get_filename_for_content(ppd_content)
482 | kw = {'operation_attributes_tag':
483 | {'printer_uri': printer_uri},
484 | 'printer_attributes_tag':
485 | {'auth_info_required': auth_info_required,
486 | 'job_sheets_default': job_sheets_default,
487 | 'device_uri': device_uri,
488 | 'port_monitor': port_monitor,
489 | 'ppd_name': ppd_name,
490 | 'printer_is_accepting_jobs': printer_is_accepting_jobs,
491 | 'printer_info': printer_info,
492 | 'printer_location': printer_location,
493 | 'printer_more_info': printer_more_info,
494 | 'printer_op_policy': printer_op_policy,
495 | 'printer_state': printer_state,
496 | 'printer_state_message': printer_state_message,
497 | 'requesting_user_name_allowed ': requesting_user_name_allowed,
498 | 'requesting_user_name_denied': requesting_user_name_denied,
499 | 'printer_is_shared': printer_is_shared,
500 | 'file': filename}}
501 |
502 | request = pretty_printer(cups_add_modify_printer_form.render(kw))
503 | try:
504 | response = yield self._call_ipptool(request)
505 | raise Return(response)
506 | finally:
507 | if delete:
508 | os.unlink(filename)
509 |
510 | @pyipptool_coroutine
511 | def cups_add_modify_class(self,
512 | printer_uri=None,
513 | auth_info_required=colander.null,
514 | member_uris=colander.null,
515 | printer_is_accepting_jobs=colander.null,
516 | printer_info=colander.null,
517 | printer_location=colander.null,
518 | printer_more_info=colander.null,
519 | printer_op_policy=colander.null,
520 | printer_state=colander.null,
521 | printer_state_message=colander.null,
522 | requesting_user_name_allowed=colander.null,
523 | requesting_user_name_denied=colander.null,
524 | printer_is_shared=colander.null):
525 | kw = {'operation_attributes_tag':
526 | {'printer_uri': printer_uri},
527 | 'printer_attributes_tag':
528 | {'auth_info_required': auth_info_required,
529 | 'member_uris': member_uris,
530 | 'printer_is_accepting_jobs': printer_is_accepting_jobs,
531 | 'printer_info': printer_info,
532 | 'printer_location': printer_location,
533 | 'printer_more_info': printer_more_info,
534 | 'printer_op_policy': printer_op_policy,
535 | 'printer_state': printer_state,
536 | 'printer_state_message': printer_state_message,
537 | 'requesting_user_name_allowed ': requesting_user_name_allowed,
538 | 'requesting_user_name_denied': requesting_user_name_denied,
539 | 'printer_is_shared': printer_is_shared}}
540 |
541 | request = pretty_printer(cups_add_modify_class_form.render(kw))
542 | response = yield self._call_ipptool(request)
543 | raise Return(response)
544 |
545 | @pyipptool_coroutine
546 | def cups_delete_printer(self, printer_uri=None):
547 | kw = {'operation_attributes_tag': {'printer_uri': printer_uri}}
548 | request = pretty_printer(cups_delete_printer_form.render(kw))
549 | response = yield self._call_ipptool(request)
550 | raise Return(response)
551 |
552 | @pyipptool_coroutine
553 | def cups_delete_class(self, printer_uri=None):
554 | kw = {'operation_attributes_tag': {'printer_uri': printer_uri}}
555 | request = pretty_printer(cups_delete_class_form.render(kw))
556 | response = yield self._call_ipptool(request)
557 | raise Return(response)
558 |
559 | @pyipptool_coroutine
560 | def cups_get_classes(self,
561 | first_printer_name=colander.null,
562 | limit=colander.null,
563 | printer_location=colander.null,
564 | printer_type=colander.null,
565 | printer_type_mask=colander.null,
566 | requested_attributes=colander.null,
567 | requested_user_name=colander.null):
568 | kw = {'operation_attributes_tag':
569 | {'first_printer_name': first_printer_name,
570 | 'limit': limit,
571 | 'printer_location': printer_location,
572 | 'printer_type': printer_type,
573 | 'printer_type_mask': printer_type_mask,
574 | 'requested_attributes': requested_attributes,
575 | 'requested_user_name': requested_user_name}}
576 | request = pretty_printer(cups_get_classes_form.render(kw))
577 | response = yield self._call_ipptool(request)
578 | raise Return(response)
579 |
580 | @pyipptool_coroutine
581 | def cups_get_devices(self,
582 | device_class=colander.null,
583 | exclude_schemes=colander.null,
584 | include_schemes=colander.null,
585 | limit=colander.null,
586 | requested_attributes=colander.null,
587 | timeout=colander.null):
588 | kw = {'operation_attributes_tag':
589 | {'device_class': device_class,
590 | 'exclude_schemes': exclude_schemes,
591 | 'include-schemes': include_schemes,
592 | 'limit': limit,
593 | 'requested_attributes': requested_attributes,
594 | 'timeout': timeout}}
595 | request = pretty_printer(cups_get_devices_form.render(kw))
596 | response = yield self._call_ipptool(request)
597 | raise Return(response)
598 |
599 | @pyipptool_coroutine
600 | def cups_get_ppd(self, printer_uri=colander.null, ppd_name=colander.null):
601 | kw = {'operation_attributes_tag':
602 | {'printer_uri': printer_uri,
603 | 'ppd_name': ppd_name,
604 | }}
605 | request = pretty_printer(cups_get_ppd_form.render(kw))
606 | response = yield self._call_ipptool(request)
607 | raise Return(response)
608 |
609 | @pyipptool_coroutine
610 | def cups_get_ppds(self,
611 | exclude_schemes=colander.null,
612 | include_schemes=colander.null,
613 | limit=colander.null,
614 | ppd_make=colander.null,
615 | ppd_make_and_model=colander.null,
616 | ppd_model_number=colander.null,
617 | ppd_natural_language=colander.null,
618 | ppd_product=colander.null,
619 | ppd_psversion=colander.null,
620 | ppd_type=colander.null,
621 | requested_attributes=colander.null):
622 | kw = {'operation_attributes_tag':
623 | {'exclude_schemes': exclude_schemes,
624 | 'include_schemes': include_schemes,
625 | 'limit': limit,
626 | 'ppd_make': ppd_make,
627 | 'ppd_make_and_model': ppd_make_and_model,
628 | 'ppd_model_number': ppd_model_number,
629 | 'ppd_natural_language': ppd_natural_language,
630 | 'ppd_product': ppd_product,
631 | 'ppd_psversion': ppd_psversion,
632 | 'ppd_type': ppd_type,
633 | 'requested_attributes': requested_attributes
634 | }}
635 | request = pretty_printer(cups_get_ppds_form.render(kw))
636 | response = yield self._call_ipptool(request)
637 | raise Return(response)
638 |
639 | @pyipptool_coroutine
640 | def cups_get_printers(self,
641 | first_printer_name=colander.null,
642 | limit=colander.null,
643 | printer_location=colander.null,
644 | printer_type=colander.null,
645 | printer_type_mask=colander.null,
646 | requested_attributes=colander.null,
647 | requested_user_name=colander.null):
648 | kw = {'operation_attributes_tag':
649 | {'first_printer_name': first_printer_name,
650 | 'limit': limit,
651 | 'printer_location': printer_location,
652 | 'printer_type': printer_type,
653 | 'printer_type_mask': printer_type_mask,
654 | 'requested_attributes': requested_attributes,
655 | 'requested_user_name': requested_user_name}}
656 | request = pretty_printer(cups_get_printers_form.render(kw))
657 | response = yield self._call_ipptool(request)
658 | raise Return(response)
659 |
660 | @pyipptool_coroutine
661 | def cups_move_job(self,
662 | printer_uri=colander.null,
663 | job_id=colander.null,
664 | job_uri=colander.null,
665 | job_printer_uri=None,
666 | printer_state_message=colander.null):
667 | kw = {'operation_attributes_tag':
668 | {'printer_uri': printer_uri,
669 | 'job_id': job_id,
670 | 'job_uri': job_uri},
671 | 'job_attributes_tag':
672 | {'job_printer_uri': job_printer_uri,
673 | 'printer_state_message': printer_state_message}}
674 | request = pretty_printer(cups_move_job_form.render(kw))
675 | response = yield self._call_ipptool(request)
676 | raise Return(response)
677 |
678 | @pyipptool_coroutine
679 | def cups_reject_jobs(self,
680 | printer_uri=None,
681 | requesting_user_name=None,
682 | printer_state_message=colander.null):
683 | kw = {'operation_attributes_tag':
684 | {'printer_uri': printer_uri,
685 | 'requesting_user_name': requesting_user_name},
686 | 'printer_attributes_tag':
687 | {'printer_state_message': printer_state_message}}
688 | request = pretty_printer(cups_reject_jobs_form.render(kw))
689 | response = yield self._call_ipptool(request)
690 | raise Return(response)
691 |
692 | @pyipptool_coroutine
693 | def get_job_attributes(self,
694 | printer_uri=colander.null,
695 | job_id=colander.null,
696 | job_uri=colander.null,
697 | requesting_user_name=colander.null,
698 | requested_attributes=colander.null):
699 | kw = {'operation_attributes_tag':
700 | {'printer_uri': printer_uri,
701 | 'job_id': job_id,
702 | 'job_uri': job_uri,
703 | 'requesting_user_name': requesting_user_name,
704 | 'requested_attributes': requested_attributes}}
705 | request = pretty_printer(get_job_attributes_form.render(kw))
706 | response = yield self._call_ipptool(request)
707 | raise Return(response)
708 |
709 | @pyipptool_coroutine
710 | def get_jobs(self,
711 | printer_uri=None,
712 | requesting_user_name=colander.null,
713 | limit=colander.null,
714 | requested_attributes=colander.null,
715 | which_jobs=colander.null,
716 | my_jobs=colander.null):
717 | kw = {'operation_attributes_tag':
718 | {'printer_uri': printer_uri,
719 | 'requesting_user_name': requesting_user_name,
720 | 'limit': limit,
721 | 'requested_attributes': requested_attributes,
722 | 'which_jobs': which_jobs,
723 | 'my_jobs': my_jobs}}
724 | request = pretty_printer(get_jobs_form.render(kw))
725 | response = yield self._call_ipptool(request)
726 | raise Return(response)
727 |
728 | @pyipptool_coroutine
729 | def get_printer_attributes(self,
730 | printer_uri=None,
731 | requesting_user_name=colander.null,
732 | requested_attributes=colander.null):
733 | kw = {'operation_attributes_tag':
734 | {'printer_uri': printer_uri,
735 | 'requesting_user_name': requesting_user_name,
736 | 'requested_attributes': requested_attributes}}
737 | request = pretty_printer(get_printer_attributes_form.render(kw))
738 | response = yield self._call_ipptool(request)
739 | raise Return(response)
740 |
741 | @pyipptool_coroutine
742 | def get_subscriptions(self,
743 | printer_uri=None,
744 | requesting_user_name=colander.null,
745 | notify_job_id=colander.null,
746 | limit=colander.null,
747 | requested_attributes=colander.null,
748 | my_subscriptions=colander.null):
749 | kw = {'operation_attributes_tag':
750 | {'printer_uri': printer_uri,
751 | 'requesting_user_name': requesting_user_name,
752 | 'notify_job_id': notify_job_id,
753 | 'limit': limit,
754 | 'requested_attributes': requested_attributes,
755 | 'my_subscriptions': my_subscriptions}}
756 | request = pretty_printer(get_subscriptions_form.render(kw))
757 | response = yield self._call_ipptool(request)
758 | raise Return(response)
759 |
760 | @pyipptool_coroutine
761 | def get_notifications(self,
762 | printer_uri=None,
763 | notify_subscription_ids=None,
764 | requesting_user_name=colander.null,
765 | notify_sequence_numbers=colander.null,
766 | notify_wait=colander.null):
767 | kw = {'operation_attributes_tag':
768 | {'printer_uri': printer_uri,
769 | 'requesting_user_name': requesting_user_name,
770 | 'notify_subscription_ids': notify_subscription_ids,
771 | 'notify_sequence_numbers': notify_sequence_numbers,
772 | 'notify_wait': notify_wait}}
773 | request = pretty_printer(get_notifications_form.render(kw))
774 | response = yield self._call_ipptool(request)
775 | raise Return(response)
776 |
777 | @pyipptool_coroutine
778 | def cancel_subscription(self,
779 | printer_uri=None,
780 | requesting_user_name=colander.null,
781 | notify_subscription_id=None):
782 | kw = {'operation_attributes_tag':
783 | {'printer_uri': printer_uri,
784 | 'requesting_user_name': requesting_user_name,
785 | 'notify_subscription_id': notify_subscription_id}}
786 | request = pretty_printer(cancel_subscription_form.render(kw))
787 | response = yield self._call_ipptool(request)
788 | raise Return(response)
789 |
790 | @pyipptool_coroutine
791 | def _pause_or_resume_printer(self, form, printer_uri=None,
792 | requesting_user_name=colander.null):
793 | kw = {'operation_attributes_tag':
794 | {'printer_uri': printer_uri,
795 | 'requesting_user_name': requesting_user_name}}
796 | request = pretty_printer(form.render(kw))
797 | response = yield self._call_ipptool(request)
798 | raise Return(response)
799 |
800 | def pause_printer(self, *args, **kw):
801 | return self._pause_or_resume_printer(pause_printer_form, *args, **kw)
802 |
803 | def resume_printer(self, *args, **kw):
804 | return self._pause_or_resume_printer(resume_printer_form, *args, **kw)
805 |
806 | @pyipptool_coroutine
807 | def _hold_or_release_new_jobs(self, form, printer_uri=None,
808 | requesting_user_name=colander.null,
809 | printer_message_from_operator=colander.null):
810 | kw = {'operation_attributes_tag':
811 | {'printer_uri': printer_uri,
812 | 'requesting_user_name': requesting_user_name,
813 | 'printer_message_from_operator': printer_message_from_operator
814 | }}
815 | request = pretty_printer(form.render(kw))
816 | response = yield self._call_ipptool(request)
817 | raise Return(response)
818 |
819 | def hold_new_jobs(self, *args, **kw):
820 | return self._hold_or_release_new_jobs(hold_new_jobs_form, *args, **kw)
821 |
822 | def release_held_new_jobs(self, *args, **kw):
823 | return self._hold_or_release_new_jobs(release_held_new_jobs_form,
824 | *args, **kw)
825 |
826 | @pyipptool_coroutine
827 | def send_document(self,
828 | job_uri=colander.null,
829 | printer_uri=colander.null,
830 | job_id=colander.null,
831 | requesting_user_name=None,
832 | document_name=colander.null,
833 | compression=colander.null,
834 | document_format='application/pdf',
835 | document_natural_language=colander.null,
836 | last_document=True,
837 | document_content=None,
838 | ):
839 | """
840 | :param document_content: Binary Content or Named File
841 | """
842 | delete = False
843 | filename, delete = _get_filename_for_content(document_content)
844 | kw = {'operation_attributes_tag':
845 | {'job_uri': job_uri,
846 | 'printer_uri': printer_uri,
847 | 'job_id': job_id,
848 | 'requesting_user_name': requesting_user_name,
849 | 'document_name': document_name,
850 | 'compression': compression,
851 | 'document_format': document_format,
852 | 'document_natural_language': document_natural_language,
853 | 'last_document': last_document},
854 | 'document_attributes_tag': {'file': filename}}
855 | request = pretty_printer(send_document_form.render(kw))
856 | try:
857 | response = yield self._call_ipptool(request)
858 | raise Return(response)
859 | finally:
860 | if delete:
861 | os.unlink(filename)
862 |
863 |
864 | class AsyncIPPToolWrapper(IPPToolWrapper):
865 | async = True
866 |
867 | def __init__(self, config, io_loop):
868 | self.config = config
869 | self.io_loop = io_loop
870 |
871 | @coroutine
872 | def _call_ipptool(self, request):
873 | with tempfile.NamedTemporaryFile(delete=False) as temp_file:
874 | temp_file.write(bytes(request, encoding='utf-8'))
875 | from tornado.process import Subprocess
876 | process = Subprocess([self.config['ipptool_path'],
877 | self.authenticated_uri, '-X',
878 | temp_file.name],
879 | stdin=subprocess.PIPE,
880 | stdout=Subprocess.STREAM,
881 | stderr=Subprocess.STREAM,
882 | io_loop=self.io_loop)
883 | future = []
884 | self.io_loop.add_timeout(self.io_loop.time() + self.config['timeout'],
885 | functools.partial(self.timeout_handler,
886 | process.proc, future))
887 | try:
888 | stdout, stderr = yield [Task(process.stdout.read_until_close),
889 | Task(process.stderr.read_until_close)]
890 | if future:
891 | raise TimeoutError
892 | finally:
893 | os.unlink(temp_file.name)
894 |
895 | result = plistlib.readPlistFromString(stdout)
896 | try:
897 | raise Return(result['Tests'][0])
898 | except (IndexError, KeyError):
899 | logger = logging.getLogger(__name__)
900 | logger.error('ipptool command failed: {} {}'.format(stdout,
901 | stderr))
902 | raise
903 |
--------------------------------------------------------------------------------