================
PyPI integration
================

..contents ::

The test fixture provides a PloneSoftwareCenter instance in the root, 
called 'psc'::

    >>> portal.psc
    <PloneSoftwareCenter at /plone/psc>

PyPI api
========

The PyPI Api is bundled as a view in the software center::

    >>> from Products.PloneSoftwareCenter.browser.pypi import PyPIView 

This allows distutils to interact with PloneSoftwareCenter like it does
with PyPI. The current implemented APIs are:

- `file_upload`: called on `upload` command
- `submit`: called on `register` command
- `list_classifiers`: called on `register --list-classifiers` 
- `verify`: called on `register --dry-run `

- a `links` page is provided through the view for easy_install to scan
  the packages releases.

Classifiers
===========

The classification of eggs are done by the Trove classification. This 
classification has a default list of values managed by the official PyPI.
When a PloneSoftwareCenter is created, it will use this list by
default (which is the recommended classification), but it can change 
it in order to deal with a custom list::

    >>> print '\n'.join(portal.psc.availableClassifiers)
    1-planning|1 - Planning|Development Status :: 1 - Planning
    ...
    utilities|Utilities|Topic :: Utilities

This is also available in the view, as `list_classifiers`, in the shape
waited by `register`::
    
    >>> from zope.publisher.browser import TestRequest
    >>> view = PyPIView(portal.psc, TestRequest())
    >>> print view.list_classifiers()
    Development Status :: 1 - Planning
    ...
    Topic :: Utilities

The initialization of a PloneSoftwareCenter will try to load the Trove
from PyPI. If the call fails, it will use the TROVE.txt file. This is done
in order to keep an up-to-date Trove because it is changing from time
to time.

Registering a release
=====================

Let's create a setuptools-like request (using sdist)::
    
    >>> form = {'': 'submit', 'license': 'GPL', 'name': 'iw.dist', 
    ...         'metadata_version': '1.0', 'author': 'Ingeniweb', 
    ...         'home_page': 'UNKNOWN', 'download_url': 'UNKNOWN', 
    ...         'summary': 'xxx', 'author_email': 'support@ingeniweb.com', 
    ...         'version': '0.1.0dev-r6983', 'platform': 'UNKNOWN', 
    ...         'keywords': '', 
    ...         'classifiers': ['Programming Language :: Python',
    ...                         'Topic :: Utilities',
    ...                         'Rated :: PG13'], 
    ...         'description': 'xxx'}
    >>> request = TestRequest(form=form)

Let's call the `submit` method::

    >>> view = PyPIView(portal.psc, request)
    >>> print view.submit()
    Created Project: iw.dist
    Updated Project: iw.dist
    Created Release 0.1.0dev-r6983 in Project iw.dist
    Updated Project: iw.dist
    Updated Release: 0.1.0dev-r6983

Let's check the result. We should have a new project in the PSC::

    >>> project = portal.psc['iw.dist']
    >>> project
    <PSCProject at /plone/psc/iw.dist>
    >>> project.title
    u'iw.dist'
   
    >>> project.getContactAddress()
    'mailto:support@ingeniweb.com'

And the right classification list (PG-13 is not a known classifier in this PSC)::

    >>> project.getClassifiers()
    ('python', 'utilities')

(These are the ids of the trove classifiers)

A download link was created because the field was set to UNKNOWN in the
request. Let's define the download url and submit the package again::

    >>> form['download_url'] = 'http://example.com/package.tgz'
    >>> request = TestRequest(form=form)
    >>> view = PyPIView(portal.psc, request) 
    >>> print view.submit()
    Updated Project: iw.dist
    Updated Project: iw.dist
    Updated Release: 0.1.0dev-r6983
    Created Download Link
    Updated Download Link

Verifying metadata before releasing
===================================

`register` has a `dry-run` mode that calls the `verify` API on the server.

Let's try it::

    >>> form = {'': 'verify', 'license': 'GPL', 'name': 'iw.dist', 
    ...         'metadata_version': '1.0', 'author': 'Ingeniweb', 
    ...         'home_page': 'UNKNOWN', 'download_url': 'UNKNOWN', 
    ...         'summary': 'summary', 'author_email': 
    ...         'support@ingeniweb.com', 'version': '0.1.0dev-r6992', 
    ...         'platform': 'UNKNOWN', 'keywords': '', 'classifiers': 
    ...          ['Programming Language :: Python', ], 
    ...          'description': 'desc'} 
    >>> request = TestRequest(form=form)

Let's call the `submit` method::

    >>> view = PyPIView(portal.psc, request)
    >>> print view.verify()
    OK
 
Uploading a release
===================

Let's create a setuptools-like request (using sdist)::

    >>> from zope.publisher.browser import TestRequest
    >>> form = {'comment': '', '': 'file_upload', 'protcol_version': '1', 
    ...         'md5_digest': '26031b40e5bcc3e7966e503eed029a41',
    ...         'filetype': 'sdist', 'pyversion': '', 
    ...         'content': tarball, 'version': '0.1cdev-r6553', 
    ...         'name': 'iw.recipe.fss', 
    ...         'description': 'this is **rest**'}
    >>> request = TestRequest(form=form)

Let's call the `file_upload` method::

    >>> view = PyPIView(portal.psc, request)
    >>> print view.file_upload()
    Created Release File: project-macosx-10.3-fat.tar.gz
    Updated Release File: project-macosx-10.3-fat.tar.gz
 
Let's check the result. We should have a new project in the PSC::

    >>> project = portal.psc['iw.recipe.fss']
    >>> project
    <PSCProject at /plone/psc/iw.recipe.fss>
    >>> project.title
    u'iw.recipe.fss'

This project now is the owner of the distutils id `iw.recipe.fss`::

    >>> project.getDistutilsMainId()
    'iw.recipe.fss'

And we have a release with the file::

    >>> releases = project.getReleaseFolder()
    >>> release = releases['0.1cdev-r6553']
    >>> release
    <PSCRelease at /plone/psc/iw.recipe.fss/releases/0.1cdev-r6553>
    >>> release.objectIds()
    ['project-macosx-10.3-fat.tar.gz']
    >>> file_ = release['project-macosx-10.3-fat.tar.gz']
    >>> file_.Title()
    'project-macosx-10.3-fat.tar.gz'
    >>> file_.platform
    u'All platforms'

The release is published as an alpha release when it's uploaded as it has a
development version number::

    >>> portal.portal_workflow.getInfoFor(release, 'review_state')
    'alpha'

Now we can modify our version to be a final release::

    >>> form['version'] = '0.1c'
    >>> form['md5_digest'] = 'd41d8cd98f00b204e9800998ecf8427e'
    >>> request = TestRequest(form=form)
    >>> view = PyPIView(portal.psc, request)
    >>> print view.file_upload()
    Created Release File: project-macosx-10.3-fat.tar.gz
    Updated Release File: project-macosx-10.3-fat.tar.gz

And we should have a nice front page with a reStructuredText content. Let's
check this with a test browser, to make sure it has been parsed like
in the Cheeseshop::

    >>> from Products.Five.testbrowser import Browser
    >>> browser = Browser()
    >>> browser.handleErrors = False
    >>> browser.open('http://localhost/plone/psc/iw.recipe.fss')
    >>> 'this is <strong>rest</strong>' in browser.contents
    True

This release should be final

    >>> release = releases['0.1c']
    >>> portal.portal_workflow.getInfoFor(release, 'review_state')
    'final'

Let's add an unknown classifier::

    >>> form['classifiers'] = ['Programming Language :: Python', 'unkown']
    >>> del form['md5_digest']

And upload again::

    >>> request = TestRequest(form=form)
    >>> view = PyPIView(portal.psc, request)
    >>> print view.file_upload()
    Updated Release File: project-macosx-10.3-fat.tar.gz

The Software Center ignored the unkown classifier. 

Secondary eggs
==============

A project can hold more than one egg. This one doesn't 
have any secondary eggs yet::

    >>> project.getDistutilsSecondaryIds()
    ()

Let's make this project the owner of `iw.fss` as well::

    >>> project.setDistutilsSecondaryIds(('iw.fss',)) 

Now let's upload the egg::

    >>> form = {'comment': '', '': 'file_upload', 'protcol_version': '1', 
    ...         'md5_digest': 'd41d8cd98f00b204e9800998ecf8427e',
    ...         'filetype': 'sdist', 'pyversion': '', 
    ...         'content': tarball, 'version': '0.2.3', 
    ...         'name': 'iw.fss', 
    ...         'description': 'this is **not rest**'}
    >>> request = TestRequest(form=form)
    >>> view = PyPIView(portal.psc, request)
    >>> print view.file_upload()
    Created Release File: project-macosx-10.3-fat.tar.gz
    Updated Release File: project-macosx-10.3-fat.tar.gz

We should have many releases now in this single project::

    >>> project.releases.objectIds() 
    ['0.1cdev-r6553', '0.1c', 'iw.fss-0.2.3']

The main egg doesn't have a prefix so PloneHelpCenter works with it.
Secondary eggs have the name of the package as a prefix for the release id.

Getting the release
===================

The PSC provides a `links` view that lists all releases that are published::

    >>> browser.open('http://localhost/plone/psc/links')
    >>> print browser.contents
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head>
      <title>List of available downloads</title>
     </head>
     <body>
      <p>This page is generated automatically by PloneSoftwareCenter</p>
    <BLANKLINE>
       <a title="http://example.com/package.tgz" href="http://example.com/package.tgz">http://example.com/package.tgz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1c/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1cdev-r6553/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/iw.fss-0.2.3/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
     </body>
    </html>
    <BLANKLINE>

The old `simple` view is kept for compatibility::

    >>> browser.open('http://localhost/plone/psc/simple')
    >>> print browser.contents
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <BLANKLINE>
     <head>
      <title>List of available downloads</title>
     </head>
     <body>
      <p>This page is generated automatically by PloneSoftwareCenter</p>
    <BLANKLINE>
       <a title="http://example.com/package.tgz" href="http://example.com/package.tgz">http://example.com/package.tgz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1c/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1cdev-r6553/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/iw.fss-0.2.3/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a><br />
    <BLANKLINE>
     </body>
    </html>
    <BLANKLINE>

Simple Index
============

A simple index is also provided, that implements the package index API
(see http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api). 
It is available through the `++simple++` namespace. `easy_install` visits 
the link for a given project, and gets its links::

    >>> browser.open('http://localhost/plone/psc/++simple++')
    >>> print browser.contents 
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head>
      <title>Simple Index</title>
     </head>
     <body>
      <p>This page is generated automatically by PloneSoftwareCenter</p>
    <BLANKLINE>
       <a title="iw.dist" href="http://localhost/plone/psc/++simple++/iw.dist">iw.dist</a><br />
    <BLANKLINE>
    <BLANKLINE>
       <a title="iw.recipe.fss" href="http://localhost/plone/psc/++simple++/iw.recipe.fss">iw.recipe.fss</a><br />
    <BLANKLINE>
    <BLANKLINE>
     </body>
    </html>
    <BLANKLINE>

    
Let's get the page for iw.recipe.fss::

    >>> browser.open('http://localhost/plone/psc/++simple++/iw.recipe.fss')
    >>> print browser.contents 
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head>
      <title>Links for iw.recipe.fss</title>
     </head>
     <body>
      <p>This page is generated automatically by PloneSoftwareCenter</p>
      <h1>Links for iw.recipe.fss</h1>
    <BLANKLINE>
      <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1c/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a>
    <BLANKLINE>
    <BLANKLINE>
      <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/0.1cdev-r6553/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a>
    <BLANKLINE>
    <BLANKLINE>
      <a title="project-macosx-10.3-fat.tar.gz" href="http://localhost/plone/psc/iw.recipe.fss/releases/iw.fss-0.2.3/project-macosx-10.3-fat.tar.gz">project-macosx-10.3-fat.tar.gz</a>
    <BLANKLINE>
     </body>
    </html>
    <BLANKLINE>

If a project doesn't have a distutils id, it will not appear in the index::

    >>> fss = portal.psc['iw.recipe.fss']
    >>> fss.setDistutilsMainId('')
    >>> browser.open('http://localhost/plone/psc/++simple++')
    >>> print browser.contents 
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head>
      <title>Simple Index</title>
     </head>
     <body>
      <p>This page is generated automatically by PloneSoftwareCenter</p>
    <BLANKLINE>
       <a title="iw.dist" href="http://localhost/plone/psc/++simple++/iw.dist">iw.dist</a><br />
    <BLANKLINE>
     </body>
    </html>
    <BLANKLINE>
    
Visible changes in UI
=====================

When `useClassifiers` is checked, the UI is using classifiers instead of
categories in all APIs::

    >>> portal.psc.setUseClassifiers(True)
    
Let's view the PSC with the classifiers flavor::

    >>> browser.open('http://localhost/plone/psc')
    >>> print browser.contents
    <!DOCTYPE ...>
    ...
    <a href="http://localhost/plone/psc/by-category/utilities">Utilities</a>
    ...
    <a href="http://localhost/plone/psc/iw.dist" ...>
    ...
    iw.dist 0.1.0dev-r6983
    ...
    </html>

Let's go to the by-category view::

    >>> browser.open('http://localhost/plone/psc/by-category/utilities')
    >>> 'iw.dist' in browser.contents
    True

The project page category displays *all* categories in its header::

    >>> browser.open('http://localhost/plone/psc/iw.dist')
    >>> page = browser.contents
    >>> 'Category: Python, Utilities' in page
    True

Distutils namespace owners
==========================


If you are not the owner of a distutils id, you cannot upload an egg 
for this id in PSC. An owner in that case, is the manager of the project
that holds this id in the `distutilsMainId` field *or* `secondaryDistutilsIds` one.

So if another user tries to upload an egg, it will fail.
Let's change the user::

    >>> self.logout()

Lets' try to upload `iw.fss`::
 
    >>> form = {'comment': '', '': 'file_upload', 'protcol_version': '1', 
    ...         'md5_digest': 'd41d8cd98f00b204e9800998ecf8427e',
    ...         'filetype': 'sdist', 'pyversion': '', 
    ...         'content': tarball, 'version': '0.2.3', 
    ...         'name': 'iw.fss', 
    ...         'description': 'this is **not rest**'}
    >>> request = TestRequest(form=form)
    >>> view = PyPIView(portal.psc, request)
    >>> print view.file_upload()
    Unauthorized


