[Subversion] / PyDicia / README.txt  

Diff of /PyDicia/README.txt

Parent Directory | Revision Log

version 2301, Thu Apr 5 05:15:28 2007 UTC version 2507, Wed Mar 5 16:52:09 2008 UTC
Line 2 
Line 2 
 Package Shipment and Address Verification with ``PyDicia``  Package Shipment and Address Verification with ``PyDicia``
 ==========================================================  ==========================================================
   
   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.  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,  Using DAZzle's XML interface, PyDicia can be used to print shipping labels,
 envelopes, postcards and more, with or without prepaid US postage indicia  envelopes, postcards and more, with or without prepaid US postage indicia
Line 10 
Line 25 
 In addition to providing a layer of syntax sugar for the DAZzle XML interface,  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  PyDicia provides a novel adaptive interface that lets you smoothly integrate
 its functions with your application's core types (like invoice, customer, or  its functions with your application's core types (like invoice, customer, or
 packing ticket objects) without subclassing.  (This is particularly useful if  "packing slip" objects) without subclassing.  (This is particularly useful if
 you are extending a CRM or other database that was written by somebody else.)  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 already used it to print about a dozen international shipping
   labels to almost as many countries.  However, the API is 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 I just want to get this milestone out to start with.  Reading the
   DAZzle XML API specification is a good idea if you want to use this; make sure
   you get at least the 7.0.x version, as that's required.
   
 PyDicia uses the ElementTree, simplegeneric, and DecoratorTools packages, and  PyDicia uses the ElementTree, simplegeneric, and DecoratorTools packages, and
 requires Python 2.4 or higher.  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.
   
 TODO:  .. contents:: **Table of Contents**
   
 * ``~`` operator for DocInfo, make DocInfo public  
   
 * global defaults  -----------------
   Developer's Guide
   -----------------
   
 * Cmd-line and queue mode handlers  
   
 * Response parsing and application  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>
   
 -----------------  
 Developer's Guide  
 -----------------  
   
 Basic Use  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.
   
   
 Application Integration  Invoking DAZzle
 =======================  ===============
   
 DocInfo yielding, Status handling, Address updating, ...  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  Advanced Customization
 ======================  ======================
   
 Using DocInfo elements  XXX Using Option elements, add_to_package()
   
   
 -----------------  -----------------
Line 55 
Line 518 
 Basic Package Options  Basic Package Options
 =====================  =====================
   
 MailClass(text), NoPostage  XXX MailClass(text), NoPostage
   DateAdvance(), Today, Tomorrow
   Value()
   Description()
   WeightOz()
   
 DateAdvance  
 WeightOz  
 Value  
 Description  
   
 Addresses  Addresses
 =========  =========
   
 ToName(text), ToTitle(text), ToCompany(text)  ::
 ToAddress(*lines)      >>> ToName("Phillip J. Eby")
 ToCity(text), ToState(text), ToPostalCode(text), ToZIP4(text), ToCountry(text)      ToName('Phillip J. Eby')
   
 ReturnAddress(*lines)      >>> 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)  ToDeliveryPoint(text)
 EndorsementLine(text)  EndorsementLine(text)
 ToCarrierRoute(text)  ToCarrierRoute(text)
 ToReturnCode(text)  
   
   
 Service Options  Package Details
 ===============  ===============
   
 DomesticFlatRateEnvelope  XXX PackageType()
 DomesticFlatRateBox  FlatRateEnvelope
   FlatRateBox
   RectangularParcel
   NonRectangularParcel
   Postcard
   Flat
   Envelope
   Width(), Length(), Depth()
   NonMachinable
   BalloonRate
   
 ReplyPostage  
   Service Options
   ===============
   
   XXX ReplyPostage
 Stealth  Stealth
 Oversize  
 SignatureWaiver  SignatureWaiver
 NoWeekendDelivery  NoWeekendDelivery
 NoHolidayDelivery  NoHolidayDelivery
 ReturnToSender  ReturnToSender
   
 RegisteredMail  
 Insurance.USPS  Insurance.USPS
 Insurance.Endicia  Insurance.Endicia
 Insurance.UPIC  Insurance.UPIC
 CertifiedMail  Insurance.NONE
 RestrictedDelivery  Services.RegisteredMail
 CertificateOfMailing  Services.CertifiedMail
 ReturnReceipt  Services.RestrictedDelivery
 DeliveryConfirmation  Services.CertificateOfMailing
 SignatureConfirmation  Services.ReturnReceipt
 COD  Services.DeliveryConfirmation
   Services.SignatureConfirmation
   Services.COD
   Services.InsuredMail()
   
   
 Customs Forms  Customs Forms
 =============  =============
   
 Sample  When processing international shipments, you will usually need to specify a
 Gift  customs form, contents type, and items.  Additionally, if you want to print
 Documents  the customs forms already "signed", you can specify a signer and the
 Other  certification option.
 Merchandise  
   Contents Types
 Customs.GEM(ctype, *items)  --------------
 Customs.CN22(ctype, *items)  
 Customs.CP72(ctype, *items)  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(formtype, ctype, *items)  
   
 Item(desc, weight, value, qty=1, origin='United States')  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  Processing Options
 ==================  ==================
   
 Test  XXX DAZzle.Test
 Layout(filename)  DAZzle.Layout(filename)
 Print  DAZzle.OutputFile(filename)
 Verify  DAZzle.Print
 SkipUnverified  DAZzle.Verify
 AutoClose  DAZzle.SkipUnverified
 Prompt  DAZzle.AutoClose
 AbortOnError  DAZzle.Prompt
 AutoPrintCustomsForms  DAZzle.AbortOnError
   DAZzle.AutoPrintCustomsForms
   DAZzle.XMLDirectory
   DAZzle.LayoutDirectory
   DAZzle.exe_path
   
   
 Miscellaneous  Miscellaneous
 =============  =============
   
 RubberStamp(n, text)  XXX RubberStamp(n, text)
 ReferenceID(text)  ReferenceID(text)
 CostCenter(int)  CostCenter(int)
   
Line 150 
Line 792 
 Internals and Tests  Internals and Tests
 -------------------  -------------------
   
 DocInfo applications::  Misc imports for tests::
   
       >>> from pydicia import add_to_package, ET, Option, Batch, Package
   
     >>> from pydicia import docinfo_to_etree, ET, _DocInfo, make_tree  Packages::
   
     >>> root = ET.Element('DAZzle')      >>> b = Batch()
     >>> pkg = ET.SubElement(root, 'Package', ID='1')      >>> p = Package(b)
   
     >>> print ET.tostring(root)      >>> print b.tostring()
     <DAZzle><Package ID="1" /></DAZzle>      <DAZzle>
           <Package ID="1" />
       </DAZzle>
   
     >>> Box = _DocInfo('FlatRate', 'BOX')      >>> Box = Option('FlatRate', 'BOX')
     >>> docinfo_to_etree(Box, root, False)      >>> add_to_package(Box, p, False)
   
     >>> print ET.tostring(root)      >>> print b.tostring()
     <DAZzle><Package ID="1"><FlatRate>BOX</FlatRate></Package></DAZzle>      <DAZzle>
           <Package ID="1">
               <FlatRate>BOX</FlatRate>
           </Package>
       </DAZzle>
   
     >>> Envelope = _DocInfo('FlatRate', 'TRUE')      >>> Envelope = Option('FlatRate', 'TRUE')
     >>> docinfo_to_etree(Envelope, root, False)      >>> add_to_package(Envelope, p, False)
     Traceback (most recent call last):      Traceback (most recent call last):
       ...        ...
     DocinfoConflict: Can't set 'FlatRate=TRUE' when 'FlatRate=BOX' already set      OptionConflict: Can't set 'FlatRate=TRUE' when 'FlatRate=BOX' already set
   
     >>> print ET.tostring(root)  
     <DAZzle><Package ID="1"><FlatRate>BOX</FlatRate></Package></DAZzle>  
   
     >>> docinfo_to_etree(Box, root, False)  
     >>> print ET.tostring(root)  
     <DAZzle><Package ID="1"><FlatRate>BOX</FlatRate></Package></DAZzle>  
   
     >>> docinfo_to_etree(Envelope, root, True)      >>> print b.tostring()
     >>> print ET.tostring(root)      <DAZzle>
     <DAZzle><Package ID="1"><FlatRate>BOX</FlatRate></Package></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>
   
     >>> del pkg[-1]      >>> add_to_package(Option('DAZzle', 'PRINTING', 'Start'), p, False)
     >>> print ET.tostring(root)      Traceback (most recent call last):
     <DAZzle><Package ID="1" /></DAZzle>        ...
       OptionConflict: Can't set 'DAZzle.Start=PRINTING' when 'DAZzle.Start=DAZ' already set
     >>> verify_zip = _DocInfo('DAZzle', 'DAZ', 'Start')  
   
     >>> docinfo_to_etree(verify_zip, root, False)      >>> b = Batch()
     >>> print ET.tostring(root)      >>> p = Package(b)
     <DAZzle Start="DAZ"><Package ID="1" /></DAZzle>      >>> 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 />
   
     >>> docinfo_to_etree(_DocInfo('DAZzle', 'PRINTING', 'Start'), root, False)      >>> b.add_package(FlatRateEnvelope, FlatRateBox)
     Traceback (most recent call last):      Traceback (most recent call last):
       ...        ...
     DocinfoConflict: Can't set 'DAZzle.Start=PRINTING' when 'DAZzle.Start=DAZ' already set      OptionConflict: Can't set 'PackageType=FLATRATEBOX' when
                                  'PackageType=FLATRATEENVELOPE' already set
   
     >>> root = ET.Element('DAZzle')      >>> print b.tostring()  # rollback on error
     >>> pkg = ET.SubElement(root, 'Package', ID='1')      <DAZzle />
     >>> print ET.tostring(root)  
     <DAZzle><Package ID="1" /></DAZzle>  
   
     >>> docinfo_to_etree([verify_zip, Envelope], root, False)  
     >>> print ET.tostring(root)  
     <DAZzle Start="DAZ"><Package ID="1"><FlatRate>TRUE</FlatRate></Package></DAZzle>  
   
   Misc shipment and postprocessing::
   
     >>> root = make_tree([(Box,), (Envelope,)], verify_zip)      >>> s = Shipment(verify_zip)
     >>> print ET.tostring(root) # doctest: +NORMALIZE_WHITESPACE      >>> s.add_package(Box)
     <DAZzle Start="DAZ"><Package      >>> s.add_package(Envelope)
     ID="1"><FlatRate>BOX</FlatRate></Package><Package      >>> root, = s.batches
     ID="2"><FlatRate>TRUE</FlatRate></Package></DAZzle>      >>> 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_docinfo()`` generic function yields "docinfo" objects for an  The ``iter_options()`` generic function yields "option" objects for an
 application object.  The default implementation is to raise an error::  application object.  The default implementation is to raise an error::
   
     >>> from pydicia import iter_docinfo      >>> from pydicia import iter_options
   
     >>> iter_docinfo(27)      >>> iter_options(27)
     Traceback (most recent call last):      Traceback (most recent call last):
       ...        ...
     NotImplementedError: ('No docinfo producer registered for', <type 'int'>)      NotImplementedError: ('No option producer registered for', <type 'int'>)
   
 And for lists and tuples, the default is to yield their contents::  And for lists and tuples, the default is to yield their contents::
   
     >>> list(iter_docinfo((1, 2, 3)))      >>> list(iter_options((1, 2, 3)))
     [1, 2, 3]      [1, 2, 3]
   
     >>> list(iter_docinfo(['a', 'b']))      >>> list(iter_options(['a', 'b']))
     ['a', 'b']      ['a', 'b']
   
 This routine is used internally by ``docinfo_to_etree()``.  This routine is used internally by ``add_to_package()``.
   
   


Generate output suitable for use with a patch program
Legend:
Removed from v.2301  
changed lines
  Added in v.2507

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help