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 <[EMAIL PROTECTED]> :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 mailing list [email protected] http://lists.ibiblio.org/mailman/listinfo/cc-devel
