cc-devel AT lists.ibiblio.org
Subject: Developer discussion for Creative Commons technology and tools
List archive
- From: "Nathan R. Yergler" <nathan AT creativecommons.org>
- To: "cc-devel AT lists.ibiblio.org" <cc-devel AT lists.ibiblio.org>
- Subject: [cc-devel] Improved Python wrapper library
- Date: Thu, 8 Mar 2007 08:36:49 -0500
I've been working with a few different CC applications over the past
month, and have realized that we have a few different ways of wrapping
and manipulating license metadata. In some cases (such as the API and
License Engine), we have code that does the same thing in two
different ways. This sucks.
I spent some time last night writing up a description of a unified
cc.license API that would replace a lot of our existing code. The
document is attached and is written in the form doctests
(http://docs.python.org/lib/module-doctest.html) which describe how
someone would use the API for working with licenses. If anyone on the
list uses Python to work with CC licenses, I'd appreciate any feedback
you have. I don't have a time table for implementing this, but wanted
to let people see where I think this is going.
There are a few areas that are less than vapor: handling of work
metadata, the question/answer api and a thorough discussion of
localization. If you have ideas or feel passionately about any of
these, please feel free to contribute. The document is in our SF.net
svn repository (http://sf.net/projects/cctools) in the cc.license
module.
Comments, feedback, etc welcome.
Nathan
-----
Nathan R. Yergler
Senior Software Engineer
Creative Commons
http://wiki.creativecommons.org/User:NathanYergler
QUESTIONS:
* when retrieving a ported license, does the locale default to that
jurisdiction's locale or English?
TODO:
* work metadata
* thorough discussion of localization; in particular how it relates to
questions
[NRY: I like attributes, but may we use methods so we can pass in a locale?]
==========
cc.license
==========
:Date: $LastChangedDate: 2006-11-21 11:23:54 -0500 (Tue, 21 Nov 2006) $
:Version: $LastChangedRevision: 4737 $
:Author: Nathan R. Yergler <nathan AT creativecommons.org>
:Organization: `Creative Commons <http://creativecommons.org>`_
:Copyright:
2007, Nathan R. Yergler, Creative Commons;
licensed to the public under the `MIT license
<http://opensource.org/licenses/mit-license.php>`_.
cc.license provides a Python package for generating, reading and manipulating
Creative Commons licenses.
Installation
============
cc.license and its dependencies may be installed using `easy_install
<http://peak.telecommunity.com/DevCenter/EasyInstall>`_ (recommended) ::
$ easy_install cc.license
or by using the standard distutils setup.py::
$ python setup.py install
If installing using setup.py, `lxml <http://codespeak.net/lxml>`_
will also need to be installed.
Background Information
======================
Creative Commons provides legal licenses for creative works. These licenses
are distinguished from one another by a set of restrictions and permissions
they provide. For example, a license may restrict the creation of derivative
works or the use of the work in a commercial context.
In 2005 Creative Commons launched the Sampling License, which provides a more
restricted set of rights targeted at the remix community. These are sometimes
referred to as the "recombo" licenses for historical reasons. In addition to
the "standard" and "sampling" families of licenses, Creative Commons also
provides metadata for some additional licenses, including the `GNU GPL`_,
`GNU LGPL`_ and a `public domain dedication`_. These categories are referred
to as "license classes".
Licenses belong to different classes if they require differing pieces of
information in order to select a specific license. For example, while the
standard license requires the user to specify how derivative works are
handled,
the sampling licenses do not.
cc.license models these License Classes and Licenses as Python objects.
When modeled as an object, a License has properties which describe the rights
and restrictions associated with it. Additionally, two License objects may
by used to determine how derivative works must/could be licensed.
Usage
=====
cc.license.supports three primary operations on licenses: reading, generation,
and manipulation.
Generating Licenses
-------------------
cc.license provides a LicenseFactory class which can generate a CC license
based on settings from the user. The simplest case for generating a license
is an unported Attribution license::
>>> import cc.license
>>> factory = cc.license.LicenseFactory()
>>> license = factory.issue()
<cc.license.License object at ...>
>>> license.name
'Attribution'
The `issue()` method on the LicenseFactory is a shortcut to the `issue()`
method of the Standard LicenseClass. See information on LicenseClasses below
for information on generating license information for Sampling and other
liceness.
The license has a number of properties which describe it's characteristics::
>>> license.version
3.0
>>> license.jurisdiction
'unported'
>>> license.uri
'http://creativecommons.org/licenses/by/3.0/'
A particular legal jurisdiction may be specified when issuing a license:::
>>> fr_license = factory.issue(jurisdiction='fr')
>>> fr_license.name
u'Attribucion'
>>> fr_license.jurisdiction
'fr'
>>> fr_license.uri
'http://creativecommons.org/licenses/by/2.0/fr/'
By default the LicenseFactory will return the latest version for the selected
jurisdiction::
>>> fr_license.version
2.0
A specific version may be requested with a keyword parameter::
>>> license = factory.issue(version=2.0)
>>> license.name
'Attribution'
>>> license.version
2.0
Specifying an invalid version raises an exception:
>>> factory.issue(version=2.1)
Traceback (most recent call last):
...
cc.license.LicenseException:
License Classes
~~~~~~~~~~~~~~~
This raises the question: how does an application know which jurisdictions or
versions are available? The LicenseFactory has a set of methods which allow
you to inspect the type of information required. For example, to retrieve
a list of the valid license classes, we use the `classes` method::
>>> factory.license_classes()
[<cc.license.LicenseClass 'standard' at xxx>,
<cc.license.LicenseClass 'recombo' at xxx>
]
The LicenseClass object provides information on the available jurisdictions
and versions::
>>> sampling = factory.get_class(cc.license.classes.SAMPLING)
>>> sampling.jurisidictions
['unported', 'br']
>>> sampling.versions()
['1.0']
>>> sampling.versions(jurisdiction='br')
['1.0']
The ``versions()`` method takes a single optional parameter, ``jurisdiction``.
If ``jurisdiction`` is omitted, it defaults to the unported licenses.
>>> standard = factory.get_class(cc.license.classes.STANDARD)
>>> standard.versions()
['1.0', '2.0', '2.5', '3.0']
>>> standard.versions() == standard.versions('unported')
True
>>> standard.versions('jp')
['2.0', '2.1']
The LicenseClass object can also be used to retrieve a list of all licenses
available in that class::
>>> sampling = factory.get_class(cc.license.classes.SAMPLING)
>>> sampling.licenses()
[<cc.license.License <sampling> instance at ...>,
<cc.license.License <sampling+> instance at ...>,
<cc.license.License <nc-sampling+> instance at ...>]
License "Questions"
~~~~~~~~~~~~~~~~~~~
In order to generate a License, a set of questions must be answered. The
LicenseClass object models these questions, and provides the set of valid
responses. Note that these values are pulled directly from questions.xml;
cc.license simply wraps them as Python objects.
>>> len(sampling.questions)
2
>>> sampling.questions[0]
<cc.license.Question instance at ...>
>>> sampling.questions[0].id
'jurisdiction'
>>> sampling.questions[0].label
'Jurisdiction of your license:'
>>> sampling.questions[1]
<cc.license.Question instance at ...>
>>> sampling.questions[1].id
'sampling'
>>> sampling.questions[1].label
What flavor of Sampling license?
>>> sampling.questions[1].options
[('sampling', 'Sampling'), ('samplingplus', 'Sampling Plus'),
('ncsamplingplus', 'NonCommercial Sampling Plus')]
The LicenseClass object provides an ``issue`` method which takes a set of
keyword arguments; each argument is the ``id`` of a question. For example,
issuing an unported Sampling+ license, given the questions above::
>>> splus = sampling.issue(jurisdiction='unported', sampling='sampling+')
>>> splus
<cc.license.License <sampling+> instance at ...>
>>> splus.name
'Sampling+'
>>> splus.version
1.0
>>> splus.uri
'http://creativecommons.org/licenses/sampling+/1.0/'
Localization
~~~~~~~~~~~~
Even though the Sampling licenses have not been ported to as many
jurisdictions
as the Standard licenses, you may request information in a particular locale.
The LicenseFactory provides a convenience method for retrieving a list of
available locales::
>>> factory.locales
['en', 'en_gb', ...]
Manipulating Licenses
---------------------
There are instances where a license URI is known, with no additional
information. cc.license can provide this license through the use of look-up
methods. To lookup a license by the URI, we use the ``by_uri`` method of
the LicenseFactory; if the License is found, the appropriate License object
is returned::
>>> factory.by_uri('http://creativecommons.org/licenses/by-sa/2.5/')
<cc.license.License <by-sa> instance at ...>
If an unknown license URI is queried, cc.license raises an exception::
>>> factory.by_uri('http://example.com/license')
Traceback (most recent call last):
...
cc.license.UnknownLicenseError:
In the past some Creative Commons software has used "license codes" instead
of fully qualified URIs to denote a license. The license code is the set of
two-character strings found in the URI; for example, the license code of the
URI in the previous example is "by-sa". To support this software, cc.license
provides a lookup interface by license code.
>>> factory.by_license_code('by-sa')
<cc.license.License <by-sa> instance at ...>
The ``by_license_code`` method takes two optional parameters: a version and
a jurisdiction. If neither is provided, the newest unported version is
returned.
>>> by_sa3 = factory.by_license_code('by-sa')
>>> by_sa3.name
'Attribution-ShareAlike'
>>> by_sa3.version
'3.0'
>>> by_sa2 = factory.by_license_code('by-sa', version='2.0')
>>> by_sa2.name
'Attribution-ShareAlike'
>>> by_sa2.version
'2.0'
>>> by_sa2.uri
'http://creativecommons.org/licenses/by-sa/2.0/'
>>> de_by = factory.by_license_code('by', jurisdiction='de')
>>> de.name
'GermanGermanGerman'
>>> de.version
'2.0'
>>> de.uri
'http://creativecommons.org/licenses/by/2.0/de/'
>>> jp_by2 = factory.by_license_code('by', jurisdiction='jp', version='2.0')
>>> jp_by2.uri
'http://creativecommons.org/licenses/jp/2.0/'
The ``by_license_code`` method will raise an exception if an invalid code,
jurisdiction or version is provided::
>>> factory.by_license_code('foo')
Traceback (most recent call last):
...
cc.license.LicenseException:
>>> factory.by_license_code('by', version='3.14')
Traceback (most recent call last):
...
cc.license.LicenseException:
Combining Licenses
------------------
As shown above, License objects have a number of properties which may be
used to retrive information. In addition to information about the individual
license, multiple license objects can be used to determine available licenses
for derivative works.
For example, given a work licensed under the `Attribution-ShareAlike`_
license,
and another licensed under the `Attribution`_ license::
>>> by_sa = factory.issue(derivatives='sa')
>>> by = factory.issue()
If these two works are combined into a single derivative work, it is important
to know what restrictions (if any) are placed on the license for that work::
>>> derivative_licenses = by_sa + by
>>> derivative_licenses
[<cc.license.License <by-sa> instance at ...>,]
>>> derivative_licenses[0].name
'Attribution-ShareAlike'
The addition operation returns a list of licenses which a derivative work may
be licensed under. Note that this list may be empty. For example, when
combining a License with the NoDerivatives clause with any other:::
>>> nd = factory.issue(derivatives='no')
>>> nd.name
'Attribution-NoDerivatives'
>>> nd + by
[]
This indicates that a work licensed under the Attribution-NoDerivatives
License
may not be combined in a derivative work, regardless of the final license.
Work Metadata
-------------
XXX
Storing License Information
---------------------------
While it may be useful to manipulate Licenses as Python objects, there is
usually a need to store the license information. For many cases simply
storing
the license URI will be sufficient. For those when it is not, License objects
are pickleable using the standard `pickle` or `cPickle` library.
License objects may also be serialized as either `RDF/XML`_ or `RDFa`_. For
example::
>>> by = factory.issue()
>>> by.name, by.version
Attribution 3.0
>>> by.rdf
XXX
>>> by.rdfa
This <>work<> is license under the <a href="xxx" rel="license"...>
The RDFa is sometimes referred to as the "licensing statement", so License
objects support `statement` as a synonym for `rdfa`::
>>> by.statement == by.rdfa
True
Finally, License objects can also be used to generate metadata encoded as
`XMP`_. XMP is a format suitable for embedding in many different digital file
formats.
>> by.xmp
XXX
Reading Licenses
----------------
cc.license subsumes the functionality previously provided by the cc.tagutils
library for extracting metadata embedded in files. Note that it does *not*
provide the actual extraction code, but relies on extensions for this (see
`Extending cc.license`_).
Extending cc.license
====================
cc.license may be extended with support for reading or writing metadata from
a particular file format through the use of `entry points`_. cc.license
defines two entry points in the `cc.license` group: read_metadata and
write_metadata. In both cases the entry point "name" should be the mime-type
which the handler supports. The wild-card characters "*" and "?" may also be
used as part of the name [XXX will setuptools allow this?]. Their
interpretation is defined by the `glob`_ module in the Python standard
library.
cc.license is an extension for itself, as it provides a license reader for
reading XMP metadata from files.
See EXTENDING.txt for details on function signatures, etc.
Known Issues
============
* It's vaporware.
-
[cc-devel] Improved Python wrapper library,
Nathan R. Yergler, 03/08/2007
-
Re: [cc-devel] Improved Python wrapper library,
Paul Scott, 03/08/2007
-
Re: [cc-devel] Improved Python wrapper library,
Nathan R. Yergler, 03/08/2007
-
Re: [cc-devel] Improved Python wrapper library,
Paul Scott, 03/09/2007
- Re: [cc-devel] Improved Python wrapper library, Nathan R. Yergler, 03/09/2007
-
Re: [cc-devel] Improved Python wrapper library,
Paul Scott, 03/09/2007
-
Re: [cc-devel] Improved Python wrapper library,
Nathan R. Yergler, 03/08/2007
-
Re: [cc-devel] Improved Python wrapper library,
Paul Scott, 03/08/2007
Archive powered by MHonArc 2.6.24.