4

Currently I am doing some tests with printers in Python, what I am trying to do is to list all the printers available.

Right now I am using PyCups library which exposes several useful APIs in the Connection class. Among these there's also getPrinters():

Here's a snippet that I use and works:

>>> import cups
>>> conn = cups.Connection ()
>>> printers = conn.getPrinters ()
>>> for printer in printers:
...     print printer, printers[printer]["device-uri"]
Brother_MFC_1910W_series
Photosmart_6520_series

I was wondering if there's any method to write the above code without using any external library. I am quite sure this can't be done without using C.

Any suggestion or reference to docs would be very appreciated. Thanks

I am using Python 3

Community
  • 1
  • 1
Sid
  • 14,176
  • 7
  • 40
  • 48
  • 1
    Motivate downvotes. – Sid Jan 06 '17 at 21:18
  • 1
    OSX ships with CUPS, I guess it makes sense to use `pycups` to poke at it. There's also "core printing layer" that you could get to using `cffi` https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_aboutprinting/osxp_aboutprt.html – Dima Tisnek Jan 09 '17 at 10:01
  • 1
    What's your overall goal...? Are you trying to minimise the number of extra packages you'd need to distribute with your app (while remaining as 'pythonic' as possible)? Or are you just looking for the most 'pythonic' way of iterating printers on OS X? – Rich Jan 13 '17 at 08:54
  • @Richard I mean avoiding libraries at all and may be something that works both on OSX and Win – Sid Jan 15 '17 at 18:56
  • @Sid If you want a cross-platform solution, you need to be explicit about it in the question. – Dag Høidahl Jan 15 '17 at 20:18

2 Answers2

2

It is possible to use C libraries from Python with standard modules only. References: CUPS API, ctypes. Translating CUPS structures and calls into ctypes syntax, we get a code that works under both standard OS X Python and Python 3:

from __future__ import print_function

from ctypes import *


class cups_option_t(Structure):
    _fields_ = [
        ('name', c_char_p),
        ('value', c_char_p)
    ]


class cups_dest_t(Structure):
    _fields_ = [
        ('name', c_char_p),
        ('instance', c_char_p),
        ('is_default', c_int),
        ('num_options', c_int),
        ('cups_option_t', POINTER(cups_option_t))
    ]


cups_lib = cdll.LoadLibrary('/usr/lib/libcups.dylib')


if __name__ == '__main__':
    dests = cups_dest_t()
    dests_p = pointer(dests)    
    num_dests = cups_lib.cupsGetDests(byref(dests_p))    
    for i in range(num_dests):
        dest = dests_p[i]
        print(dest.is_default, dest.name)
        for j in range(dest.num_options):
            option = dest.cups_option_t[j]
            print('', option.name, option.value, sep='\t')    
    cups_lib.cupsFreeDests(num_dests, dests_p)

Be especially careful when using ctypes, most of errors would produce a segmentation fault.

void
  • 2,759
  • 12
  • 28
  • Well if I use CUPS than I can use the snippet I provided – Sid Jan 12 '17 at 08:12
  • 2
    What is the task then, did you mean avoiding any libraries at all? CUPS is developed by Apple and [lies beneath](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_flexible/osxp_flexible.html) all other interfaces. – void Jan 12 '17 at 12:07
  • I mean avoiding libraries at all and may be something that works both on OSX and Win – Sid Jan 15 '17 at 18:55
  • These two goals are somewhat contradictory. Libraries are developed exactly with the purpose to provide more abstract and high level API. The code above works without changes on Ubuntu 16.04. The only way to avoid using libraries is to descend to lower levels, for example to write directly into printer device driver with `io.RawIOBase.write()` and `fcntl.ioctl()`. And that automatically makes your code bound to specific architecture and non-portable. – void Jan 16 '17 at 10:52
  • With regard to cross-platform printing on both OS X and Windows: I can only think about [PyQt](https://pypi.python.org/pypi/PyQt5), it should have a binding over [Qt printing interface](http://doc.qt.io/qt-5.7/printing.html). Yet another cross-platform library. Cannot give better advise though, I didn't work with Qt for a long time. – void Jan 16 '17 at 10:54
1

You can perform a similar query using the terminal command lpstat (man for OSX). Python's builtin subprocess module would allow you to run that command and store the output. Some text parsing should provide the printer name.

from subprocess import Popen, PIPE

# "lpstat -a" prints info on printers that can accept print requests
p = Popen(['lpstat', '-a'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, errors = p.communicate()

lines = output.split('\n')
# check before you implement this parsing
printers = map(lambda x: x.split(' ')[0], lines)
Logan Byers
  • 1,454
  • 12
  • 19
  • Yeah I also thought the same thing, but I was wondering which is the 'Pythonic' method – Sid Jan 12 '17 at 08:12
  • 2
    `lpstat -a` shows only printer name and accepting state, which is not equivalent to the code above. The output is not guaranteed to have a standard format, so parsing code might break in different OS X versions. – void Jan 14 '17 at 07:17