We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Broken URL links for npm packages with special characters? 
From @calvinhp on [Slack](https://gratipay.slackarchive.io/gratipay/page-100/ts-1496325029088097): 

> I just found a 404 :slightly_smiling_face:
> If I search for Plone, the first matching project is @plone/plone-react
> but it is a 404

[Search results](https://gratipay.com/search?q=Plone): 

![screen shot 2017-06-01 at 8 00 50 pm](https://cloud.githubusercontent.com/assets/3893573/26684619/1246a946-4705-11e7-821f-04d03028cbdb.png)

Broken Link: https://gratipay.com/on/npm/@plone/plone-react/

I tried URL encoding it to be https://gratipay.com/on/npm/%40plone%2Fplone-react/, didn't work. I've encountered similar problems with common routing libraries where a `/` can't be URL-encoded and placed in the URL, although the spec supports it - might be related. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gratipay/models/package/__init__.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 from postgres.orm import Model
5 
6 from .emails import Emails
7 from .team import Team
8 
9 
10 NPM = 'npm'  # We are starting with a single package manager. If we see
11              # traction we will expand.
12 
13 
14 class Package(Model, Emails, Team):
15     """Represent a gratipackage. :-)
16 
17     Packages are entities on open source package managers; `npm
18     <https://www.npmjs.com/>`_ is the only one we support so far. Each package
19     on npm has a page on Gratipay with an URL of the form ``/on/npm/foo/``.
20     Packages can be claimed by Gratipay participants, at which point we create
21     a :py:class:`~gratipay.models.team.Team` for them under the hood so they
22     can start accepting payments.
23 
24     """
25 
26     typname = 'packages'
27 
28     def __eq__(self, other):
29         if not isinstance(other, Package):
30             return False
31         return self.id == other.id
32 
33     def __ne__(self, other):
34         if not isinstance(other, Package):
35             return True
36         return self.id != other.id
37 
38 
39     @property
40     def url_path(self):
41         """The path part of the URL for this package on Gratipay.
42         """
43         return '/on/{}/{}/'.format(self.package_manager, self.name)
44 
45 
46     @property
47     def remote_human_url(self):
48         """The URL for the main page for this package on its package manager.
49         """
50         if self.package_manager == NPM:
51             return 'https://www.npmjs.com/package/{}'.format(self.name)
52         raise NotImplementedError()
53 
54 
55     @property
56     def remote_api_url(self):
57         """The main API URL for this package on its package manager.
58         """
59         if self.package_manager == NPM:
60             return 'https://registry.npmjs.com/{}'.format(self.name)
61         raise NotImplementedError()
62 
63 
64     # Constructors
65     # ============
66 
67     @classmethod
68     def from_id(cls, id, cursor=None):
69         """Return an existing package based on id.
70         """
71         cursor = cursor or cls.db
72         return cursor.one("SELECT packages.*::packages FROM packages WHERE id=%s", (id,))
73 
74 
75     @classmethod
76     def from_names(cls, package_manager, name, cursor=None):
77         """Return an existing package based on package manager and package names.
78         """
79         cursor = cursor or cls.db
80         return cursor.one( "SELECT packages.*::packages FROM packages "
81                            "WHERE package_manager=%s and name=%s"
82                          , (package_manager, name)
83                           )
84 
85 
86     @classmethod
87     def upsert(cls, package_manager, **kw):
88         """Upsert a package. Required keyword arguments:
89 
90         - ``name`` (string)
91         - ``description`` (string)
92         - ``emails`` (list of strings)
93 
94         Optional keyword argument:
95 
96         - ``cursor``
97 
98         :return None:
99 
100         """
101         cursor = kw.pop('cursor', cls.db)
102         cursor.run('''
103         INSERT INTO packages
104                     (package_manager, name, description, emails)
105              VALUES ('npm', %(name)s, %(description)s, %(emails)s)
106 
107         ON CONFLICT (package_manager, name) DO UPDATE
108                 SET description=%(description)s, emails=%(emails)s
109         ''', kw)
110 
111 
112     def delete(self, cursor=None):
113         """Delete the package, unlinking any team (the team itself lives on)
114         and clearing any claim.
115         """
116         cursor = cursor or self.db
117         if self.load_team(cursor):
118             self.unlink_team(cursor)
119         cursor.run("DELETE FROM claims WHERE package_id=%s", (self.id,))
120         cursor.run( "DELETE FROM packages WHERE package_manager=%s AND name=%s"
121                   , (self.package_manager, self.name)
122                    )
123
```

Path: `gratipay/models/package/team.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import uuid
5 
6 from gratipay.models.team import Team as _Team
7 
8 
9 class Team(object):
10     """A :py:class:`~gratipay.models.package.Package` can have a
11     :py:class:`~gratipay.models.team.Team` associated with it.
12     """
13 
14     @property
15     def team(self):
16         """A computed attribute, the :py:class:`~gratipay.models.team.Team`
17         linked to this package if there is one, otherwise ``None``. Makes a
18         database call.
19         """
20         return self.load_team(self.db)
21 
22 
23     def load_team(self, cursor):
24         """Given a database cursor, return a
25         :py:class:`~gratipay.models.team.Team` if there is one linked to this
26         package, or ``None`` if not.
27         """
28         return cursor.one( 'SELECT t.*::teams FROM teams t WHERE t.id='
29                            '(SELECT team_id FROM teams_to_packages tp WHERE tp.package_id=%s)'
30                          , (self.id,)
31                           )
32 
33 
34     def get_or_create_linked_team(self, cursor, owner):
35         """Given a db cursor and a :py:class:`Participant`, return a
36         :py:class:`~gratipay.models.team.Team`.
37         """
38         team = self.load_team(cursor)
39         if team:
40             return team
41 
42         def slug_options():
43             yield self.name
44             for i in range(1, 10):
45                 yield '{}-{}'.format(self.name, i)
46             yield uuid.uuid4().hex
47 
48         for slug in slug_options():
49             if cursor.one('SELECT count(*) FROM teams WHERE slug=%s', (slug,)) == 0:
50                 break
51 
52         team = _Team.insert( slug=slug
53                            , slug_lower=slug.lower()
54                            , name=slug
55                            , homepage='https://www.npmjs.com/package/' + self.name
56                            , product_or_service=self.description
57                            , owner=owner
58                            , _cursor=cursor
59                             )
60         cursor.run('INSERT INTO teams_to_packages (team_id, package_id) '
61                    'VALUES (%s, %s)', (team.id, self.id))
62         self.app.add_event( cursor
63                           , 'package'
64                           , dict(id=self.id, action='link', values=dict(team_id=team.id))
65                            )
66         return team
67 
68 
69     def unlink_team(self, cursor):
70         """Given a db cursor, unlink the team associated with this package
71         (it's a bug if called with no team linked).
72         """
73         team = self.load_team(cursor)
74         assert team is not None  # sanity check
75         cursor.run('DELETE FROM teams_to_packages WHERE package_id=%s', (self.id,))
76         self.app.add_event( cursor
77                           , 'package'
78                           , dict(id=self.id, action='unlink', values=dict(team_id=team.id))
79                            )
80
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gratipay/models/package/__init__.py b/gratipay/models/package/__init__.py
--- a/gratipay/models/package/__init__.py
+++ b/gratipay/models/package/__init__.py
@@ -16,7 +16,7 @@
 
     Packages are entities on open source package managers; `npm
     <https://www.npmjs.com/>`_ is the only one we support so far. Each package
-    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo/``.
+    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo``.
     Packages can be claimed by Gratipay participants, at which point we create
     a :py:class:`~gratipay.models.team.Team` for them under the hood so they
     can start accepting payments.
@@ -40,7 +40,7 @@
     def url_path(self):
         """The path part of the URL for this package on Gratipay.
         """
-        return '/on/{}/{}/'.format(self.package_manager, self.name)
+        return '/on/{}/{}'.format(self.package_manager, self.name)
 
 
     @property
diff --git a/gratipay/models/package/team.py b/gratipay/models/package/team.py
--- a/gratipay/models/package/team.py
+++ b/gratipay/models/package/team.py
@@ -40,9 +40,12 @@
             return team
 
         def slug_options():
-            yield self.name
+            # Having analyzed existing names, we should never get `@` without
+            # `/`. Be conservative in what we accept! Oh, wait ...
+            base_name = self.name.split('/')[1] if self.name.startswith('@') else self.name
+            yield base_name
             for i in range(1, 10):
-                yield '{}-{}'.format(self.name, i)
+                yield '{}-{}'.format(base_name, i)
             yield uuid.uuid4().hex
 
         for slug in slug_options():

{"golden_diff": "diff --git a/gratipay/models/package/__init__.py b/gratipay/models/package/__init__.py\n--- a/gratipay/models/package/__init__.py\n+++ b/gratipay/models/package/__init__.py\n@@ -16,7 +16,7 @@\n \n     Packages are entities on open source package managers; `npm\n     <https://www.npmjs.com/>`_ is the only one we support so far. Each package\n-    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo/``.\n+    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo``.\n     Packages can be claimed by Gratipay participants, at which point we create\n     a :py:class:`~gratipay.models.team.Team` for them under the hood so they\n     can start accepting payments.\n@@ -40,7 +40,7 @@\n     def url_path(self):\n         \"\"\"The path part of the URL for this package on Gratipay.\n         \"\"\"\n-        return '/on/{}/{}/'.format(self.package_manager, self.name)\n+        return '/on/{}/{}'.format(self.package_manager, self.name)\n \n \n     @property\ndiff --git a/gratipay/models/package/team.py b/gratipay/models/package/team.py\n--- a/gratipay/models/package/team.py\n+++ b/gratipay/models/package/team.py\n@@ -40,9 +40,12 @@\n             return team\n \n         def slug_options():\n-            yield self.name\n+            # Having analyzed existing names, we should never get `@` without\n+            # `/`. Be conservative in what we accept! Oh, wait ...\n+            base_name = self.name.split('/')[1] if self.name.startswith('@') else self.name\n+            yield base_name\n             for i in range(1, 10):\n-                yield '{}-{}'.format(self.name, i)\n+                yield '{}-{}'.format(base_name, i)\n             yield uuid.uuid4().hex\n \n         for slug in slug_options():\n", "issue": "Broken URL links for npm packages with special characters? \nFrom @calvinhp on [Slack](https://gratipay.slackarchive.io/gratipay/page-100/ts-1496325029088097): \r\n\r\n> I just found a 404 :slightly_smiling_face:\r\n> If I search for Plone, the first matching project is @plone/plone-react\r\n> but it is a 404\r\n\r\n[Search results](https://gratipay.com/search?q=Plone): \r\n\r\n![screen shot 2017-06-01 at 8 00 50 pm](https://cloud.githubusercontent.com/assets/3893573/26684619/1246a946-4705-11e7-821f-04d03028cbdb.png)\r\n\r\nBroken Link: https://gratipay.com/on/npm/@plone/plone-react/\r\n\r\nI tried URL encoding it to be https://gratipay.com/on/npm/%40plone%2Fplone-react/, didn't work. I've encountered similar problems with common routing libraries where a `/` can't be URL-encoded and placed in the URL, although the spec supports it - might be related. \n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom postgres.orm import Model\n\nfrom .emails import Emails\nfrom .team import Team\n\n\nNPM = 'npm'  # We are starting with a single package manager. If we see\n             # traction we will expand.\n\n\nclass Package(Model, Emails, Team):\n    \"\"\"Represent a gratipackage. :-)\n\n    Packages are entities on open source package managers; `npm\n    <https://www.npmjs.com/>`_ is the only one we support so far. Each package\n    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo/``.\n    Packages can be claimed by Gratipay participants, at which point we create\n    a :py:class:`~gratipay.models.team.Team` for them under the hood so they\n    can start accepting payments.\n\n    \"\"\"\n\n    typname = 'packages'\n\n    def __eq__(self, other):\n        if not isinstance(other, Package):\n            return False\n        return self.id == other.id\n\n    def __ne__(self, other):\n        if not isinstance(other, Package):\n            return True\n        return self.id != other.id\n\n\n    @property\n    def url_path(self):\n        \"\"\"The path part of the URL for this package on Gratipay.\n        \"\"\"\n        return '/on/{}/{}/'.format(self.package_manager, self.name)\n\n\n    @property\n    def remote_human_url(self):\n        \"\"\"The URL for the main page for this package on its package manager.\n        \"\"\"\n        if self.package_manager == NPM:\n            return 'https://www.npmjs.com/package/{}'.format(self.name)\n        raise NotImplementedError()\n\n\n    @property\n    def remote_api_url(self):\n        \"\"\"The main API URL for this package on its package manager.\n        \"\"\"\n        if self.package_manager == NPM:\n            return 'https://registry.npmjs.com/{}'.format(self.name)\n        raise NotImplementedError()\n\n\n    # Constructors\n    # ============\n\n    @classmethod\n    def from_id(cls, id, cursor=None):\n        \"\"\"Return an existing package based on id.\n        \"\"\"\n        cursor = cursor or cls.db\n        return cursor.one(\"SELECT packages.*::packages FROM packages WHERE id=%s\", (id,))\n\n\n    @classmethod\n    def from_names(cls, package_manager, name, cursor=None):\n        \"\"\"Return an existing package based on package manager and package names.\n        \"\"\"\n        cursor = cursor or cls.db\n        return cursor.one( \"SELECT packages.*::packages FROM packages \"\n                           \"WHERE package_manager=%s and name=%s\"\n                         , (package_manager, name)\n                          )\n\n\n    @classmethod\n    def upsert(cls, package_manager, **kw):\n        \"\"\"Upsert a package. Required keyword arguments:\n\n        - ``name`` (string)\n        - ``description`` (string)\n        - ``emails`` (list of strings)\n\n        Optional keyword argument:\n\n        - ``cursor``\n\n        :return None:\n\n        \"\"\"\n        cursor = kw.pop('cursor', cls.db)\n        cursor.run('''\n        INSERT INTO packages\n                    (package_manager, name, description, emails)\n             VALUES ('npm', %(name)s, %(description)s, %(emails)s)\n\n        ON CONFLICT (package_manager, name) DO UPDATE\n                SET description=%(description)s, emails=%(emails)s\n        ''', kw)\n\n\n    def delete(self, cursor=None):\n        \"\"\"Delete the package, unlinking any team (the team itself lives on)\n        and clearing any claim.\n        \"\"\"\n        cursor = cursor or self.db\n        if self.load_team(cursor):\n            self.unlink_team(cursor)\n        cursor.run(\"DELETE FROM claims WHERE package_id=%s\", (self.id,))\n        cursor.run( \"DELETE FROM packages WHERE package_manager=%s AND name=%s\"\n                  , (self.package_manager, self.name)\n                   )\n", "path": "gratipay/models/package/__init__.py"}, {"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport uuid\n\nfrom gratipay.models.team import Team as _Team\n\n\nclass Team(object):\n    \"\"\"A :py:class:`~gratipay.models.package.Package` can have a\n    :py:class:`~gratipay.models.team.Team` associated with it.\n    \"\"\"\n\n    @property\n    def team(self):\n        \"\"\"A computed attribute, the :py:class:`~gratipay.models.team.Team`\n        linked to this package if there is one, otherwise ``None``. Makes a\n        database call.\n        \"\"\"\n        return self.load_team(self.db)\n\n\n    def load_team(self, cursor):\n        \"\"\"Given a database cursor, return a\n        :py:class:`~gratipay.models.team.Team` if there is one linked to this\n        package, or ``None`` if not.\n        \"\"\"\n        return cursor.one( 'SELECT t.*::teams FROM teams t WHERE t.id='\n                           '(SELECT team_id FROM teams_to_packages tp WHERE tp.package_id=%s)'\n                         , (self.id,)\n                          )\n\n\n    def get_or_create_linked_team(self, cursor, owner):\n        \"\"\"Given a db cursor and a :py:class:`Participant`, return a\n        :py:class:`~gratipay.models.team.Team`.\n        \"\"\"\n        team = self.load_team(cursor)\n        if team:\n            return team\n\n        def slug_options():\n            yield self.name\n            for i in range(1, 10):\n                yield '{}-{}'.format(self.name, i)\n            yield uuid.uuid4().hex\n\n        for slug in slug_options():\n            if cursor.one('SELECT count(*) FROM teams WHERE slug=%s', (slug,)) == 0:\n                break\n\n        team = _Team.insert( slug=slug\n                           , slug_lower=slug.lower()\n                           , name=slug\n                           , homepage='https://www.npmjs.com/package/' + self.name\n                           , product_or_service=self.description\n                           , owner=owner\n                           , _cursor=cursor\n                            )\n        cursor.run('INSERT INTO teams_to_packages (team_id, package_id) '\n                   'VALUES (%s, %s)', (team.id, self.id))\n        self.app.add_event( cursor\n                          , 'package'\n                          , dict(id=self.id, action='link', values=dict(team_id=team.id))\n                           )\n        return team\n\n\n    def unlink_team(self, cursor):\n        \"\"\"Given a db cursor, unlink the team associated with this package\n        (it's a bug if called with no team linked).\n        \"\"\"\n        team = self.load_team(cursor)\n        assert team is not None  # sanity check\n        cursor.run('DELETE FROM teams_to_packages WHERE package_id=%s', (self.id,))\n        self.app.add_event( cursor\n                          , 'package'\n                          , dict(id=self.id, action='unlink', values=dict(team_id=team.id))\n                           )\n", "path": "gratipay/models/package/team.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom postgres.orm import Model\n\nfrom .emails import Emails\nfrom .team import Team\n\n\nNPM = 'npm'  # We are starting with a single package manager. If we see\n             # traction we will expand.\n\n\nclass Package(Model, Emails, Team):\n    \"\"\"Represent a gratipackage. :-)\n\n    Packages are entities on open source package managers; `npm\n    <https://www.npmjs.com/>`_ is the only one we support so far. Each package\n    on npm has a page on Gratipay with an URL of the form ``/on/npm/foo``.\n    Packages can be claimed by Gratipay participants, at which point we create\n    a :py:class:`~gratipay.models.team.Team` for them under the hood so they\n    can start accepting payments.\n\n    \"\"\"\n\n    typname = 'packages'\n\n    def __eq__(self, other):\n        if not isinstance(other, Package):\n            return False\n        return self.id == other.id\n\n    def __ne__(self, other):\n        if not isinstance(other, Package):\n            return True\n        return self.id != other.id\n\n\n    @property\n    def url_path(self):\n        \"\"\"The path part of the URL for this package on Gratipay.\n        \"\"\"\n        return '/on/{}/{}'.format(self.package_manager, self.name)\n\n\n    @property\n    def remote_human_url(self):\n        \"\"\"The URL for the main page for this package on its package manager.\n        \"\"\"\n        if self.package_manager == NPM:\n            return 'https://www.npmjs.com/package/{}'.format(self.name)\n        raise NotImplementedError()\n\n\n    @property\n    def remote_api_url(self):\n        \"\"\"The main API URL for this package on its package manager.\n        \"\"\"\n        if self.package_manager == NPM:\n            return 'https://registry.npmjs.com/{}'.format(self.name)\n        raise NotImplementedError()\n\n\n    # Constructors\n    # ============\n\n    @classmethod\n    def from_id(cls, id, cursor=None):\n        \"\"\"Return an existing package based on id.\n        \"\"\"\n        cursor = cursor or cls.db\n        return cursor.one(\"SELECT packages.*::packages FROM packages WHERE id=%s\", (id,))\n\n\n    @classmethod\n    def from_names(cls, package_manager, name, cursor=None):\n        \"\"\"Return an existing package based on package manager and package names.\n        \"\"\"\n        cursor = cursor or cls.db\n        return cursor.one( \"SELECT packages.*::packages FROM packages \"\n                           \"WHERE package_manager=%s and name=%s\"\n                         , (package_manager, name)\n                          )\n\n\n    @classmethod\n    def upsert(cls, package_manager, **kw):\n        \"\"\"Upsert a package. Required keyword arguments:\n\n        - ``name`` (string)\n        - ``description`` (string)\n        - ``emails`` (list of strings)\n\n        Optional keyword argument:\n\n        - ``cursor``\n\n        :return None:\n\n        \"\"\"\n        cursor = kw.pop('cursor', cls.db)\n        cursor.run('''\n        INSERT INTO packages\n                    (package_manager, name, description, emails)\n             VALUES ('npm', %(name)s, %(description)s, %(emails)s)\n\n        ON CONFLICT (package_manager, name) DO UPDATE\n                SET description=%(description)s, emails=%(emails)s\n        ''', kw)\n\n\n    def delete(self, cursor=None):\n        \"\"\"Delete the package, unlinking any team (the team itself lives on)\n        and clearing any claim.\n        \"\"\"\n        cursor = cursor or self.db\n        if self.load_team(cursor):\n            self.unlink_team(cursor)\n        cursor.run(\"DELETE FROM claims WHERE package_id=%s\", (self.id,))\n        cursor.run( \"DELETE FROM packages WHERE package_manager=%s AND name=%s\"\n                  , (self.package_manager, self.name)\n                   )\n", "path": "gratipay/models/package/__init__.py"}, {"content": "# -*- coding: utf-8 -*-\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport uuid\n\nfrom gratipay.models.team import Team as _Team\n\n\nclass Team(object):\n    \"\"\"A :py:class:`~gratipay.models.package.Package` can have a\n    :py:class:`~gratipay.models.team.Team` associated with it.\n    \"\"\"\n\n    @property\n    def team(self):\n        \"\"\"A computed attribute, the :py:class:`~gratipay.models.team.Team`\n        linked to this package if there is one, otherwise ``None``. Makes a\n        database call.\n        \"\"\"\n        return self.load_team(self.db)\n\n\n    def load_team(self, cursor):\n        \"\"\"Given a database cursor, return a\n        :py:class:`~gratipay.models.team.Team` if there is one linked to this\n        package, or ``None`` if not.\n        \"\"\"\n        return cursor.one( 'SELECT t.*::teams FROM teams t WHERE t.id='\n                           '(SELECT team_id FROM teams_to_packages tp WHERE tp.package_id=%s)'\n                         , (self.id,)\n                          )\n\n\n    def get_or_create_linked_team(self, cursor, owner):\n        \"\"\"Given a db cursor and a :py:class:`Participant`, return a\n        :py:class:`~gratipay.models.team.Team`.\n        \"\"\"\n        team = self.load_team(cursor)\n        if team:\n            return team\n\n        def slug_options():\n            # Having analyzed existing names, we should never get `@` without\n            # `/`. Be conservative in what we accept! Oh, wait ...\n            base_name = self.name.split('/')[1] if self.name.startswith('@') else self.name\n            yield base_name\n            for i in range(1, 10):\n                yield '{}-{}'.format(base_name, i)\n            yield uuid.uuid4().hex\n\n        for slug in slug_options():\n            if cursor.one('SELECT count(*) FROM teams WHERE slug=%s', (slug,)) == 0:\n                break\n\n        team = _Team.insert( slug=slug\n                           , slug_lower=slug.lower()\n                           , name=slug\n                           , homepage='https://www.npmjs.com/package/' + self.name\n                           , product_or_service=self.description\n                           , owner=owner\n                           , _cursor=cursor\n                            )\n        cursor.run('INSERT INTO teams_to_packages (team_id, package_id) '\n                   'VALUES (%s, %s)', (team.id, self.id))\n        self.app.add_event( cursor\n                          , 'package'\n                          , dict(id=self.id, action='link', values=dict(team_id=team.id))\n                           )\n        return team\n\n\n    def unlink_team(self, cursor):\n        \"\"\"Given a db cursor, unlink the team associated with this package\n        (it's a bug if called with no team linked).\n        \"\"\"\n        team = self.load_team(cursor)\n        assert team is not None  # sanity check\n        cursor.run('DELETE FROM teams_to_packages WHERE package_id=%s', (self.id,))\n        self.app.add_event( cursor\n                          , 'package'\n                          , dict(id=self.id, action='unlink', values=dict(team_id=team.id))\n                           )\n", "path": "gratipay/models/package/team.py"}]}