= People and Teams =

Just as with Launchpad, the web service exposes a uniform interface to people
and teams.  In other words, people and teams occupy the same namespace.  You
treat people and teams as the same type of object, and need to inspect the
object to know whether you're dealing with a person or a team.


== People ==

You can access Launchpad people, and the set of people with the most karma,
through the web service interface.  The set of people with the most karma is
available from the service root.

    >>> from launchpadlib.testing.helpers import salgado_with_full_permissions
    >>> launchpad = salgado_with_full_permissions.login()
    >>> people = launchpad.people

The list of people is not fetched until you actually use data.

    >>> print people._wadl_resource.representation
    None

    >>> len(people)
    4

    >>> print people._wadl_resource.representation
    {...}

The 'me' attribute is also available from the service root. It's a
quick way to get a reference to your own user account.

    >>> me = launchpad.me
    >>> me.name
    u'salgado'

You can find a person by name.

    >>> salgado = launchpad.people['salgado']
    >>> salgado.name
    u'salgado'
    >>> salgado.display_name
    u'Guilherme Salgado'
    >>> salgado.is_team
    False

But if no person by that name is registered, you get the expected KeyError.

    >>> launchpad.people['not-a-registered-person']
    Traceback (most recent call last):
    ...
    KeyError: 'not-a-registered-person'

You can find a person by email.

    >>> email = salgado.preferred_email_address.email
    >>> salgado = launchpad.people.getByEmail(email=email)
    >>> salgado.name
    u'salgado'

Once you have a person, you can store their URL and use it to look
them up later.

    >>> me.self_link
    u'http://api.launchpad.dev:8085/beta/~salgado'
    >>> launchpad.load(me.self_link).name
    u'salgado'
    >>> launchpad.load('http://launchpad.dev:8085/')
    Traceback (most recent call last):
    ...
    ValueError: ... doesn't serve a JSON document.
    >>> url_without_type = ('http://api.launchpad.dev:8085/beta/people' +
    ...                     '?ws.op=find&text=salgado')
    >>> launchpad.load(url_without_type)
    Traceback (most recent call last):
    ...
    ValueError: Couldn't determine the resource type of...

You can iterate through all the people in the set.

    >>> names = sorted(person.name for person in launchpad.people)
    >>> len(names)
    4
    >>> names
    [u'carlos', u'name12', u'name16', u'sabdfl']

You can get a slice of the list of people, so long as you provide
start and end points keyed to the beginning of the list. This set-up
code creates a regular Python list of all people on the site, for
comparison with a launchpadlib Collection object representing the same
list.

    >>> all_people = [person for person in launchpad.people]
    >>> people = launchpad.people

Calling len() on the Collection object makes sure that the first page
of representations is cached, which forces this test to test an
optimization.

    >>> ignored = len(people)

These tests demonstrate that slicing the collection resource gives the
same results as collecting all the entries in the collection, and
slicing an ordinary list.

    >>> def slices_match(slice):
    ...     """Slice two lists of people, then make sure they're the same."""
    ...     list1 = people[slice]
    ...     list2 = all_people[slice]
    ...     if len(list1) != len(list2):
    ...         raise ("Lists are different sizes: %d vs. %d" %
    ...                (len(list1), len(list2)))
    ...     for index in range(0, len(list1)):
    ...         if list1[index].name != list2[index].name:
    ...             raise ("%s doesn't match %s in position %d" %
    ...                    (list1[index].name, list2[index].name, index))
    ...     return True

    >>> slices_match(slice(3))
    True
    >>> slices_match(slice(50))
    True
    >>> slices_match(slice(1,2))
    True
    >>> slices_match(slice(10,21))
    True
    >>> slices_match(slice(10,21,3))
    True

    >>> slices_match(slice(0, 200))
    True
    >>> slices_match(slice(30, 200))
    True
    >>> slices_match(slice(60, 100))
    True

    >>> people[5:]
    Traceback (most recent call last):
    ...
    ValueError: Collection slices must have a definite, nonnegative end point.

    >>> people[10:-1]
    Traceback (most recent call last):
    ...
    ValueError: Collection slices must have a definite, nonnegative end point.

    >>> people[-1:]
    Traceback (most recent call last):
    ...
    ValueError: Collection slices must have a nonnegative start point.

    >>> people[:]
    Traceback (most recent call last):
    ...
    ValueError: Collection slices must have a definite, nonnegative end point.

It's not possible to slice a single person from the top-level
collection of people. launchpadlib will try to use the value you pass
in as a person's name, which will almost always fail.

    >>> people[1]
    Traceback (most recent call last):
    ...
    KeyError: 1

You can slice a collection that's the return value of a named
operation.

    >>> a_people = launchpad.people.find(text='a')
    >>> len(a_people[1:3])
    2

You can also access individual items in this collection by
index. Unlike with the top-level collection, your index won't be
interpreted as a person's name.

    >>> a_people[1].name
    u'andrelop'

    >>> a_people[1000]
    Traceback (most recent call last):
    ...
    IndexError: list index out of range

Besides a name and a display name, a person has many other attributes that you
can read.

    XXX 05-Jun-2008 BarryWarsaw Some of these attributes are links to further
    collections and are not yet tested.  Tests will be added in future
    branches.

    >>> salgado.karma
    0
    >>> print salgado.homepage_content
    None
    >>> #salgado.mugshot
    >>> #salgado.languages
    >>> salgado.hide_email_addresses
    False
    >>> salgado.date_created
    datetime.datetime(2005, 6, 6, 8, 59, 51, 596025, ...)
    >>> print salgado.time_zone
    None
    >>> salgado.is_valid
    True
    >>> #salgado.wiki_names
    >>> #salgado.irc_nicknames
    >>> #salgado.jabber_ids
    >>> #salgado.team_memberships
    >>> #salgado.open_membership_invitations
    >>> #salgado.teams_participated_in
    >>> #salgado.teams_indirectly_participated_in
    >>> #salgado.confirmed_email_addresses
    >>> #salgado.preferred_email_address
    >>> salgado.mailing_list_auto_subscribe_policy
    u'Ask me when I join a team'
    >>> salgado.visibility
    u'Public'


== Teams ==

You also access teams using the same interface.

    >>> team = launchpad.people['ubuntu-team']
    >>> team.name
    u'ubuntu-team'
    >>> team.display_name
    u'Ubuntu Team'
    >>> team.is_team
    True

Regular people have team attributes, but they're not used.

    >>> print salgado.team_owner
    None

You can find out how a person has membership in a team.

    # XXX: salgado, 2008-08-01: Commented because method has been Unexported;
    # it should be re-enabled after the operation is exported again.
    # >>> path = salgado.findPathToTeam(
    # ...     team=launchpad.people['mailing-list-experts'])
    # >>> [team.name for team in path]
    # [u'admins', u'mailing-list-experts']

You can create a new team through the web interface.  The simplest case of
this requires only the new team's name, owner and display name.

    >>> launchpad.people['bassists']
    Traceback (most recent call last):
    ...
    KeyError: 'bassists'

    >>> bassists = launchpad.people.newTeam(
    ...     name='bassists', display_name='Awesome Rock Bass Players')
    >>> bassists.name
    u'bassists'
    >>> bassists.display_name
    u'Awesome Rock Bass Players'
    >>> bassists.is_team
    True

And of course, that team is now accessible directly.

    >>> bassists = launchpad.people['bassists']
    >>> bassists.name
    u'bassists'
    >>> bassists.display_name
    u'Awesome Rock Bass Players'

You cannot create the same team twice.

    >>> launchpad.people.newTeam(name='bassists', display_name='Bass Gods')
    Traceback (most recent call last):
    ...
    HTTPError: HTTP Error 400: Bad Request

Actually, the exception contains other useful information.

    >>> from launchpadlib.errors import HTTPError
    >>> try:
    ...     launchpad.people.newTeam(
    ...         name='bassists', display_name='Bass Gods')
    ... except HTTPError, error:
    ...     pass
    >>> error.response['status']
    '400'
    >>> error.content
    'name: bassists is already in use by another person or team.'

Besides a name and a display name, a team has many other attributes that you
can read.

    >>> bassists.karma
    0
    >>> print bassists.homepage_content
    None
    >>> bassists.hide_email_addresses
    False
    >>> bassists.date_created
    datetime.datetime(...)
    >>> print bassists.time_zone
    None
    >>> bassists.is_valid
    True
    >>> #bassists.team_memberships
    >>> #bassists.open_membership_invitations
    >>> #bassists.teams_participated_in
    >>> #bassists.teams_indirectly_participated_in
    >>> #bassists.confirmed_email_addresses
    >>> #bassists.team_owner
    >>> #bassists.preferred_email_address
    >>> #bassists.members
    >>> #bassists.admins
    >>> #bassists.participants
    >>> #bassists.deactivated_members
    >>> #bassists.expired_members
    >>> #bassists.invited_members
    >>> #bassists.member_memberships
    >>> #bassists.proposed_members
    >>> bassists.visibility
    u'Public'
    >>> print bassists.team_description
    None
    >>> bassists.subscription_policy
    u'Moderated Team'
    >>> bassists.renewal_policy
    u'invite them to apply for renewal'
    >>> print bassists.default_membership_period
    None
    >>> print bassists.default_renewal_period
    None
