Skip to main content

Print labels, envelopes, US postage, and more using the endicia.com API

Project description

New in 0.1a3
  • Added FlatRateLargeBox and ExpressMailPremiumService tags, for use with DAZzle 8.0 and up.

New in 0.1a2
  • Dropped MS Windows requirement; you can now use PyDicia to generate XML for the Endicia Mac client, or to be sent via download or networked directory to a Windows client.

  • Added a Shipment.run() method to allow running multiple batches in sequence.

  • Fixed the Status.ToZip4 attribute (it was incorrectly spelled ToZIP4, and thus didn’t work).

PyDicia is a Python interface to endicia.com’s postal services client, DAZzle. Using DAZzle’s XML interface, PyDicia can be used to print shipping labels, envelopes, postcards and more, with or without prepaid US postage indicia (electronic stamps), as well as doing address verification.

In addition to providing a layer of syntax sugar for the DAZzle XML interface, PyDicia provides a novel adaptive interface that lets you smoothly integrate its functions with your application’s core types (like invoice, customer, or “packing slip” objects) without subclassing. (This is particularly useful if you are extending a CRM or other database that was written by somebody else.)

This version of PyDicia is an alpha proof-of-concept release. It is actually usable – I’ve in fact been using it in production for over a year, shipping dozens of envelopes and packages a month all over the US, Canada, and Europe. However, the API is still potentially subject to change, the reference documentation is sketchy, and the developer’s guide lacks detail about some of the more advanced features. This should improve in future releases, but at least this way, you can use it now, if you need something like it. Reading the DAZzle XML API specification is a good idea if you want to use this, though; the 8.0 version is the latest, although PyDicia also supports 7.0.x versions.

PyDicia uses the ElementTree, simplegeneric, and DecoratorTools packages, and requires Python 2.4 or higher (due to use of decorators and the Decimal type). Actually printing any labels requires that you have an Endicia “Premium” or “Mac” account. (Note: I have not used the Mac client, so I don’t know how well it works there. See the section below on Using PyDicia on Non-Windows Platforms for more info.)

IMPORTANT

Please note that PyDicia does not attempt to implement all of the US Postal Service’s business rules for what options may be used in what combinations. It doesn’t even validate most of the DAZzle client’s documented restrictions! So it’s strictly a “Garbage In, Garbage Out” kind of deal. If you put garbage in, who knows what the heck will happen. You might end up spending lots of money and getting your packages returned to you – and I AM NOT RESPONSIBLE, even if your problem is due to an error in PyDicia or its documentation!

So, make sure you understand the shipping options you wish to use, and test your application thoroughly before using this code in production. You have been warned!

Questions, discussion, and bug reports for this software should be directed to the PEAK mailing list; see http://www.eby-sarna.com/mailman/listinfo/PEAK/ for details.

Developer’s Guide

Basic XML Generation

PyDicia simplifies the creation of XML for DAZzle by using objects to specify what data needs to go in the XML. These objects are mostly Option instances, or callables that create Option instances. However, the framework is extensible, so that you can use your own object types with the same API. Your object types can either generate Option instances, or directly manipulate the XML using ElementTree APIs for maximum control.

In the simpler cases, however, you will just use lists or tuples of objects provided by (or created with) the PyDicia API to represent packages or labels.

Batch Objects

XML documents are represented using Batch objects:

>>> from pydicia import *
>>> b = Batch()

The tostring() method of a batch returns its XML in string form, optionally in a given encoding (defaulting to ASCII if not specified):

>>> print b.tostring('latin1')
<?xml version='1.0' encoding='latin1'?>
<DAZzle />

To add a package to a batch, you use the add_package() method:

>>> b.add_package(ToName('Phillip Eby'))
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
         <ToName>Phillip Eby</ToName>
    </Package>
</DAZzle>

The add_package() method accepts zero or more objects that can manipulate PyDicia package objects. It also accepts tuples or lists of such objects, nested to arbitrary depth:

>>> b.add_package([Services.COD, (Stealth, ToName('Ty Sarna'))], FlatRateBox)

>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <ToName>Phillip Eby</ToName>
    </Package>
    <Package ID="2">
        <Services COD="ON" />
        <Stealth>TRUE</Stealth>
        <ToName>Ty Sarna</ToName>
        <PackageType>FLATRATEBOX</PackageType>
    </Package>
</DAZzle>

And the packages attribute of a batch keeps track of the arguments that have been passed to add_package():

>>> b.packages
[Package(ToName('Phillip Eby'),),
 Package([Services.COD('ON'), (Stealth('TRUE'), ToName('Ty Sarna'))],
  PackageType('FLATRATEBOX'))]

Each package object in the list wraps a tuple of the arguments that were supplied for each invocation of add_package(). This allows the system to send status updates (including delivery confirmation numbers, customs IDs, etc.) back to the application.

But before we can process status updates, we need to have some application objects, as described in the next section.

Using Your Application Objects as Package Sources

In addition to PyDicia-defined objects and sequences thereof, the add_package() method also accepts any custom objects of your own design that have been registered with the pydicia.add_to_package() or pydicia.iter_options() generic functions:

>>> class Customer:
...     def __init__(self, **kw):
...         self.__dict__ = kw

>>> @iter_options.when_type(Customer)
... def cust_options(ob):
...     yield ToName(ob.name)
...     yield ToAddress(ob.address)
...     yield ToCity(ob.city)
...     yield ToState(ob.state)
...     yield ToPostalCode(ob.zip)

>>> b = Batch()
>>> c = Customer(
...     name='PJE', address='123 Nowhere Dr', state='FL', city='Nowhere',
...     zip='12345-6789'
... )
>>> b.add_package(c)
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <ToName>PJE</ToName>
        <ToAddress1>123 Nowhere Dr</ToAddress1>
        <ToCity>Nowhere</ToCity>
        <ToState>FL</ToState>
        <ToPostalCode>12345-6789</ToPostalCode>
    </Package>
</DAZzle>

This allows you to pass customer, package, product, invoice, or other application-specific objects into add_package(). And the objects yielded by your iter_options implementation can also be application objects, e.g.:

>>> class Invoice:
...     def __init__(self, **kw):
...         self.__dict__ = kw

>>> @iter_options.when_type(Invoice)
... def invoice_options(ob):
...     yield ob.shippingtype
...     yield ob.products
...     yield ob.customer

>>> b = Batch()
>>> i = Invoice(
...     shippingtype=(Tomorrow, MailClass('MEDIAMAIL')),
...     products=[WeightOz(27),], customer=c
... )
>>> b.add_package(i)
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <DateAdvance>1</DateAdvance>
        <MailClass>MEDIAMAIL</MailClass>
        <WeightOz>27</WeightOz>
        <ToName>PJE</ToName>
        <ToAddress1>123 Nowhere Dr</ToAddress1>
        <ToCity>Nowhere</ToCity>
        <ToState>FL</ToState>
        <ToPostalCode>12345-6789</ToPostalCode>
    </Package>
</DAZzle>

Note that there is no particular significance to my choice of lists vs. tuples in these examples; they’re more to demonstrate that you can use arbitrary structures, as long as they contain objects that are supported by either iter_options() or add_to_package(). Normally, you will simply use collections of either PyDicia-provided symbols, or application objects for which you’ve defined an iter_options() method.

You will also usually want to implement your PyDicia support in a module by itself, so you can use from pydicia import * without worrying about symbol collisions.

Batch-wide Options

When you create a batch, you can pass in any number of objects, to specify options that will be applied to every package. For example, this batch will have every package set to be mailed tomorrow as media mail:

>>> b = Batch( Tomorrow, MailClass('MEDIAMAIL') )
>>> b.add_package(ToName('PJE'))
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <ToName>PJE</ToName>
        <DateAdvance>1</DateAdvance>
        <MailClass>MEDIAMAIL</MailClass>
    </Package>
</DAZzle>

Multi-Batch Shipments

Certain DAZzle options can only be set once per XML file, such as the choice of layout file. If you are shipping multiple packages with different label layouts (such as domestic vs. international mail), you need to separate these packages into different batches, each of which will be in a separate XML file. The Shipment class handles this separation for you automatically.

When you create a shipment, it initially has no batches:

>>> s = Shipment()
>>> s.batches
[]

But as you add packages to it, it will create batches as needed:

>>> s.add_package(ToName('Phillip Eby'), DAZzle.Test)
>>> len(s.batches)
1

>>> print s.batches[0].tostring()
<DAZzle Test="YES">
    <Package ID="1">
        <ToName>Phillip Eby</ToName>
    </Package>
</DAZzle>

As long as you’re adding packages with the same or compatible options, the same batch will be reused:

>>> s.add_package(ToName('Ty Sarna'), DAZzle.Test)
>>> len(s.batches)
1
>>> print s.batches[0].tostring()
<DAZzle Test="YES">
    <Package ID="1">
        <ToName>Phillip Eby</ToName>
    </Package>
    <Package ID="2">
        <ToName>Ty Sarna</ToName>
    </Package>
</DAZzle>

But as soon as you add a package with any incompatible options, a new batch will be created and used:

>>> s.add_package(ToName('PJE'), ~DAZzle.Test)
>>> len(s.batches)
2

>>> print s.batches[1].tostring()
<DAZzle Test="NO">
    <Package ID="1">
        <ToName>PJE</ToName>
    </Package>
</DAZzle>

And each time you add a package, it’s added to the first compatible batch:

>>> s.add_package(ToName('Some Body'), ~DAZzle.Test)
>>> len(s.batches)
2

>>> print s.batches[1].tostring()
<DAZzle Test="NO">
    <Package ID="1">
        <ToName>PJE</ToName>
    </Package>
    <Package ID="2">
        <ToName>Some Body</ToName>
    </Package>
</DAZzle>

>>> s.add_package(ToName('No Body'), DAZzle.Test)
>>> len(s.batches)
2

>>> print s.batches[0].tostring()
<DAZzle Test="YES">
    <Package ID="1">
        <ToName>Phillip Eby</ToName>
    </Package>
    <Package ID="2">
        <ToName>Ty Sarna</ToName>
    </Package>
    <Package ID="3">
        <ToName>No Body</ToName>
    </Package>
</DAZzle>

By the way, as with batches, you can create a shipment with options that will be applied to all packages:

>>> s = Shipment(Tomorrow, Services.COD)
>>> s.add_package(ToName('Some Body'), DAZzle.Test)
>>> s.add_package(ToName('No Body'), ~DAZzle.Test)
>>> len(s.batches)
2
>>> print s.batches[0].tostring()
<DAZzle Test="YES">
    <Package ID="1">
        <ToName>Some Body</ToName>
        <DateAdvance>1</DateAdvance>
        <Services COD="ON" />
    </Package>
</DAZzle>

>>> print s.batches[1].tostring()
<DAZzle Test="NO">
    <Package ID="1">
        <ToName>No Body</ToName>
        <DateAdvance>1</DateAdvance>
        <Services COD="ON" />
    </Package>
</DAZzle>

Receiving Status Updates

When DAZzle completes a batch, it creates an output file containing status information for each package in the batch. If you’d like to process this status information for the corresponding application objects you passed in to add_package(), you can extend the report_status() generic function to do this:

>>> @report_status.when_type(Customer)
... def customer_status(ob, status):
...     print ob
...     print status

>>> b = Batch()
>>> b.add_package(c)

When the batch receives status information, it will invoke report_status() on each package’s application items, with a status object for the corresponding package:

>>> b.report_statuses()
<...Customer instance...>
ToAddress           : [u'123 Nowhere Dr']
ToCity              : u'Nowhere'
ToState             : u'FL'
ToPostalCode        : u'12345-6789'
ToAddress1          : u'123 Nowhere Dr'

Note that you don’t normally need to call report_statuses() directly; it’s usually done for you as part of the process of running a batch or shipment. (See the section below on Invoking DAZzle.)

The status object passed to your method will be a Status instance with attributes similar to those above, containing USPS-normalized address data. In addition, several other fields are possible:

>>> from pydicia import ET
>>> b.etree = ET.fromstring('''
... <DAZzle><Package ID="1">
...     <ToZip4>1234</ToZip4>
...     <Status>Rejected (-3)</Status>
...     <PIC>123465874359</PIC>
...     <FinalPostage>4.60</FinalPostage>
...     <TransactionDateTime>20070704173221</TransactionDateTime>
...     <PostmarkDate>20070705</PostmarkDate>
... </Package></DAZzle>''')

>>> b.report_statuses()
<...Customer instance...>
Status              : 'Rejected (-3)'
ErrorCode           : -3
ToAddress           : []
ToZip4              : '1234'
PIC                 : '123465874359'
FinalPostage        : Decimal("4.60")
TransactionDateTime : datetime.datetime(2007, 7, 4, 17, 32, 21)
PostmarkDate        : datetime.date(2007, 7, 5)

The Status object should support all output fields supported by DAZzle; see the DAZzle documentation for details. The non-string fields shown above are the only ones which are postprocessed to specialized Python objects; the rest are kept as strings or Unicode values. The ErrorCode field is computed by extracting the integer portion of any rejection code. It is None in the case of a successful live print, and 0 in the case of a successful test print. See the DAZzle XML interface documentation for a description of other error codes.

Note that for a more compact presentation, attributes with None values are not included in the str() of a Status object, which is why the statuses displayed above show different sets of fields. The attributes, however, always exist; they simply have None as their value.

Invoking DAZzle

In the simplest case, invoking a batch or shipment object’s .run() method will launch a local copy of DAZzle on a temporary file containing the batch’s XML, wait for DAZzle to exit, then process status updates from the output file and return DAZzle’s return code. (Or a list of return codes, in the case of a Shipment.)

If you are using this approach, you may wish to include ~DAZzle.Prompt (which keeps end-user prompts to a minimum) and DAZzle.AutoClose (so that DAZzle exits upon completion of the batch) in your batch options.

If you do not have a local copy of DAZzle, but instead are using a network queue directory to send jobs remotely, you can instead use the batch object’s .write(queuedir) method to send the batch to the queue. You can also use this approach to send jobs to a local copy of DAZzle running in the background.

If a copy of DAZzle is installed locally, you can get its XML queue directory from DAZzle.XMLDirectory, and check whether it is monitoring for files using DAZzle.get_preference("MonitorXML"). (These values will be None if DAZzle is not installed.)

If DAZzle is installed locally, you can launch it with the DAZzle.run(args=(), sync=True) function. The args are a list of command line arguments to pass, and sync is a flag indicating whether to wait for DAZzle to exit. If sync is a false value, run() returns a subprocess.Popen instance. Otherwise, it returns the process’s exit code (ala subprocess.call).

XXX async batch status retrieval

XXX DAZzle.exe_path, DAZzle.get_preference(), DAZzle.LayoutDirectory

XXX Launching for multi-batch, remote, queued, and other async processing

Using PyDicia on Non-Windows Platforms

When used on a non-Windows platform, PyDicia cannot detect any DAZzle configuration information, so you must manually set DAZzle.exe_path to the client program, if you wish to use any of the run() methods. (Likewise, you must manually set DAZzle.LayoutDirectory if you want layout paths to be automatically adjusted.)

On the Mac, the exe_path should be set to a program that takes a single XML filename as an argument. The Mac endiciatool program probably will not work on its own, without a wrapper shell script of some kind; I’m open to suggestions as to how to improve this. (Note, by the way, that the Mac client doesn’t support all of the options that the Windows client does, so remember that use of PyDicia is entirely at your own risk, whatever the platform!)

On other platforms, the main usefulness of PyDicia would be in generating XML for users to download (e.g. from a web application) or submitting and processing jobs via a Samba-mounted queue directory. You don’t need an exe_path for this, but you will need to generate your own layout and output file paths using Option objects, to avoid them being mangled by PyDicia’s platform-specific path munging.

XXX explain how to do that, or make it work anyway

Advanced Customization

XXX Using Option elements, add_to_package()

Options Reference

Basic Package Options

XXX MailClass(text), NoPostage DateAdvance(), Today, Tomorrow Value() Description() WeightOz()

Addresses

::
>>> ToName("Phillip J. Eby")
ToName('Phillip J. Eby')
>>> ToTitle("President")
ToTitle('President')
>>> ToCompany("Dirt Simple, Inc.")
ToCompany('Dirt Simple, Inc.')

XXX ToAddress(*lines) ToCity(text), ToState(text), ToPostalCode(text), ToZIP4(text), ToCountry(text)

XXX ReturnAddress(*lines) ToDeliveryPoint(text) EndorsementLine(text) ToCarrierRoute(text)

Package Details

XXX PackageType() FlatRateEnvelope FlatRateBox RectangularParcel NonRectangularParcel Postcard Flat Envelope Width(), Length(), Depth() NonMachinable BalloonRate

Service Options

XXX ReplyPostage Stealth SignatureWaiver NoWeekendDelivery NoHolidayDelivery ReturnToSender Insurance.USPS Insurance.Endicia Insurance.UPIC Insurance.NONE Services.RegisteredMail Services.CertifiedMail Services.RestrictedDelivery Services.CertificateOfMailing Services.ReturnReceipt Services.DeliveryConfirmation Services.SignatureConfirmation Services.COD Services.InsuredMail()

Customs Forms

When processing international shipments, you will usually need to specify a customs form, contents type, and items. Additionally, if you want to print the customs forms already “signed”, you can specify a signer and the certification option.

Contents Types

The ContentsType constructor defines the type of contents declared on the customs form. There are six predefined constants for the standard contents types:

>>> Customs.Sample
ContentsType('SAMPLE')

>>> Customs.Gift
ContentsType('GIFT')

>>> Customs.Documents
ContentsType('DOCUMENTS')

>>> Customs.Other
ContentsType('OTHER')

>>> Customs.Merchandise
ContentsType('MERCHANDISE')

>>> Customs.ReturnedGoods
ContentsType('RETURNEDGOODS')

Customs Form Types

The CustomsFormType constructor defines the type of customs form to be used. There are four predefined constants for the allowed form types:

>>> Customs.GEM
CustomsFormType('GEM')

>>> Customs.CN22
CustomsFormType('CN22')

>>> Customs.CP72
CustomsFormType('CP72')

>>> Customs.NONE
CustomsFormType('NONE')

Customs Items

Items to be declared on a customs form are created using Customs.Item. The minimum required arguments are a description, a unit weight in ounces (which must be an integer or decimal), and a value in US dollars (also an integer or decimal):

>>> from decimal import Decimal
>>> i = Customs.Item("Paperback book", 12, Decimal('29.95'))

You may also optionally specify a quantity (which must be an integer) and a country of origin. The defaults for these are 1 and "United States", respectively:

>>> i
Item('Paperback book', Decimal("12"), Decimal("29.95"), 1, 'United States')

You always specify a unit weight and value; these are automatically multiplied by the quantity on the customs form, and for purposes of calculating total weight/value.

Note that a package’s total weight must be greater than or equal to the sum of its items’ weight, and its value must exactly equal the sum of its items’ values:

>>> b = Batch()
>>> b.add_package(i)
Traceback (most recent call last):
  ...
OptionConflict: Total package weight must be specified when Customs.Items
                are used

>>> b.add_package(i, WeightOz(1))
Traceback (most recent call last):
  ...
OptionConflict: Total item weight is 12 oz, but
                total package weight is only 1 oz

>>> b.add_package(i, WeightOz(12), Value(69))
Traceback (most recent call last):
  ...
OptionConflict: Can't set 'Value=29.95' when 'Value=69' already set

And a form type and contents type must be specified if you include any items:

>>> b.add_package(i, WeightOz(12))
Traceback (most recent call last):
  ...
OptionConflict: Customs form + content type must be specified with items

>>> b.add_package(i, WeightOz(12), Customs.Gift)
Traceback (most recent call last):
  ...
OptionConflict: Customs form + content type must be specified with items

>>> b.add_package(i, WeightOz(12), Customs.CN22)
Traceback (most recent call last):
  ...
OptionConflict: Customs form + content type must be specified with items

>>> b.add_package(i, WeightOz(12), Customs.Gift, Customs.CN22)
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <CustomsQuantity1>1</CustomsQuantity1>
        <CustomsCountry1>United States</CustomsCountry1>
        <CustomsDescription1>Paperback book</CustomsDescription1>
        <CustomsWeight1>12</CustomsWeight1>
        <CustomsValue1>29.95</CustomsValue1>
        <WeightOz>12</WeightOz>
        <ContentsType>GIFT</ContentsType>
        <CustomsFormType>CN22</CustomsFormType>
        <Value>29.95</Value>
    </Package>
</DAZzle>

The final customs form will include the multiplied-out weights and values based on the quantity of each item:

>>> b = Batch()
>>> b.add_package(
...     Customs.Item('x',23,42,3), Customs.Item('y',1,7),
...     WeightOz(99), Customs.Gift, Customs.CN22
... )
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <CustomsQuantity1>3</CustomsQuantity1>
        <CustomsCountry1>United States</CustomsCountry1>
        <CustomsDescription1>x</CustomsDescription1>
        <CustomsWeight1>69</CustomsWeight1>
        <CustomsValue1>126</CustomsValue1>
        <CustomsQuantity2>1</CustomsQuantity2>
        <CustomsCountry2>United States</CustomsCountry2>
        <CustomsDescription2>y</CustomsDescription2>
        <CustomsWeight2>1</CustomsWeight2>
        <CustomsValue2>7</CustomsValue2>
        <WeightOz>99</WeightOz>
        <ContentsType>GIFT</ContentsType>
        <CustomsFormType>CN22</CustomsFormType>
        <Value>133</Value>
    </Package>
</DAZzle>

Customs Signature

You can specify the person who’s certifying the customs form using these options:

>>> Customs.Signer("Phillip Eby")
CustomsSigner('Phillip Eby')

>>> Customs.Certify
CustomsCertify('TRUE')

Processing Options

XXX DAZzle.Test DAZzle.Layout(filename) DAZzle.OutputFile(filename) DAZzle.Print DAZzle.Verify DAZzle.SkipUnverified DAZzle.AutoClose DAZzle.Prompt DAZzle.AbortOnError DAZzle.AutoPrintCustomsForms DAZzle.XMLDirectory DAZzle.LayoutDirectory DAZzle.exe_path

Miscellaneous

XXX RubberStamp(n, text) ReferenceID(text) CostCenter(int)

Internals and Tests

Misc imports for tests:

>>> from pydicia import add_to_package, ET, Option, Batch, Package

Packages:

>>> b = Batch()
>>> p = Package(b)

>>> print b.tostring()
<DAZzle>
    <Package ID="1" />
</DAZzle>

>>> Box = Option('FlatRate', 'BOX')
>>> add_to_package(Box, p, False)

>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <FlatRate>BOX</FlatRate>
    </Package>
</DAZzle>

>>> Envelope = Option('FlatRate', 'TRUE')
>>> add_to_package(Envelope, p, False)
Traceback (most recent call last):
  ...
OptionConflict: Can't set 'FlatRate=TRUE' when 'FlatRate=BOX' already set

>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <FlatRate>BOX</FlatRate>
    </Package>
</DAZzle>

>>> add_to_package(Box, p, False)
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <FlatRate>BOX</FlatRate>
    </Package>
</DAZzle>

>>> add_to_package(Envelope, p, True)
>>> print b.tostring()
<DAZzle>
    <Package ID="1">
        <FlatRate>BOX</FlatRate>
    </Package>
</DAZzle>

>>> del p.element[-1]; p.element.text=''
>>> print b.tostring()
<DAZzle>
    <Package ID="1" />
</DAZzle>

>>> verify_zip = Option('DAZzle', 'DAZ', 'Start')

>>> add_to_package(verify_zip, p, False)
>>> print b.tostring()
<DAZzle Start="DAZ">
    <Package ID="1" />
</DAZzle>

>>> add_to_package(Option('DAZzle', 'PRINTING', 'Start'), p, False)
Traceback (most recent call last):
  ...
OptionConflict: Can't set 'DAZzle.Start=PRINTING' when 'DAZzle.Start=DAZ' already set

>>> b = Batch()
>>> p = Package(b)
>>> add_to_package([verify_zip, Envelope], p, False)
>>> print b.tostring()
<DAZzle Start="DAZ">
    <Package ID="1">
        <FlatRate>TRUE</FlatRate>
    </Package>
</DAZzle>

>>> p.should_queue(Services.COD)
True
>>> print b.tostring()
<DAZzle Start="DAZ">
    <Package ID="1">
        <FlatRate>TRUE</FlatRate>
    </Package>
</DAZzle>

>>> p.finish()
>>> print b.tostring()
<DAZzle Start="DAZ">
    <Package ID="1">
        <FlatRate>TRUE</FlatRate>
        <Services COD="ON" />
    </Package>
</DAZzle>

>>> p.should_queue(Services.COD)
False

Batch rollback:

>>> b = Batch()
>>> print b.tostring()
<DAZzle />

>>> b.add_package(FlatRateEnvelope, FlatRateBox)
Traceback (most recent call last):
  ...
OptionConflict: Can't set 'PackageType=FLATRATEBOX' when
                           'PackageType=FLATRATEENVELOPE' already set

>>> print b.tostring()  # rollback on error
<DAZzle />

Misc shipment and postprocessing:

>>> s = Shipment(verify_zip)
>>> s.add_package(Box)
>>> s.add_package(Envelope)
>>> root, = s.batches
>>> print root.tostring()
<DAZzle Start="DAZ">
    <Package ID="1">
        <FlatRate>BOX</FlatRate>
    </Package>
    <Package ID="2">
        <FlatRate>TRUE</FlatRate>
    </Package>
</DAZzle>

Option inversion:

>>> ~Envelope
FlatRate('FALSE')
>>> ~~Envelope
FlatRate('TRUE')

>>> ~Option('Services', 'ON', 'RegisteredMail')
Services.RegisteredMail('OFF')
>>> ~~Option('Services', 'ON', 'RegisteredMail')
Services.RegisteredMail('ON')

>>> ~Option('DAZzle', 'YES', 'Prompt')
DAZzle.Prompt('NO')
>>> ~~Option('DAZzle', 'YES', 'Prompt')
DAZzle.Prompt('YES')

The iter_options() generic function yields “option” objects for an application object. The default implementation is to raise an error:

>>> from pydicia import iter_options

>>> iter_options(27)
Traceback (most recent call last):
  ...
NotImplementedError: ('No option producer registered for', <type 'int'>)

And for lists and tuples, the default is to yield their contents:

>>> list(iter_options((1, 2, 3)))
[1, 2, 3]

>>> list(iter_options(['a', 'b']))
['a', 'b']

This routine is used internally by add_to_package().

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

PyDicia-0.1a3.zip (39.7 kB view hashes)

Uploaded Source

Built Distributions

PyDicia-0.1a3-py2.5.egg (34.6 kB view hashes)

Uploaded Source

PyDicia-0.1a3-py2.4.egg (35.0 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page