← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/inteam-db-calls-0 into lp:launchpad/devel

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/inteam-db-calls-0 into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #591544 person.inTeam(other_person) hits the db (when both not teams)
  https://bugs.launchpad.net/bugs/591544


This is my branch to remove useless db checks from IPerson.inTeam().

    lp:~sinzui/launchpad/inteam-db-calls-0
    Diff size: 178
    Launchpad bug:
          https://bugs.launchpad.net/bugs/id
    Test command: ./bin/test -vv -t TestPersonTeams
    Pre-implementation: no one.
    Target release: 10.10


Remove useless db checks from IPerson.inTeam()
----------------------------------------------

person.inTeam(other_person) hits the db when other_person is not a team.



Rules
-----

    * Return early if the team is a user.
    * The test will duplicate the team cache tests in doctests...remove
      the doctest version because the cache is an implementation detail.


QA
--

None.


Lint
----

Linting changed files:
  lib/lp/registry/doc/person.txt
  lib/lp/registry/doc/teammembership.txt
  lib/lp/registry/model/person.py
  lib/lp/registry/tests/test_person.py


Lint hated this file and I will fix it before I land.
./lib/lp/registry/doc/teammembership.txt
       1: narrative uses a moin header.
      60: narrative uses a moin header.
     141: source exceeds 78 characters.
     417: narrative uses a moin header.
     555: narrative uses a moin header.
     687: want exceeds 78 characters.
     716: narrative uses a moin header.
     789: narrative uses a moin header.
     836: narrative uses a moin header.
     881: want exceeds 78 characters.
     892: narrative uses a moin header.
     945: narrative uses a moin header.
     634: 'Store' imported but unused


Test
----

    * lib/lp/registry/doc/person.txt
      * Removed implementation details from doctest.
    * lib/lp/registry/doc/teammembership.txt
      * Removed implementation details from doctest.
    * lib/lp/registry/tests/test_person.py
      * Added unittests for inTeam and its cache
      * Added a test to verify that when the team is a person that the
        method returns before querying the db and building the cache.


Implementation
--------------

    * lib/lp/registry/model/person.py
      * Added a check to return early when team argument is a user.
-- 
https://code.launchpad.net/~sinzui/launchpad/inteam-db-calls-0/+merge/34932
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/inteam-db-calls-0 into lp:launchpad/devel.
=== modified file 'lib/lp/registry/doc/person.txt'
--- lib/lp/registry/doc/person.txt	2010-07-27 22:13:36 +0000
+++ lib/lp/registry/doc/person.txt	2010-09-08 22:31:05 +0000
@@ -628,31 +628,6 @@
     >>> vcs_imports.hasParticipationEntryFor(vcs_imports)
     True
 
-The inTeam method is cached to avoid unnecessary database lookups - this
-was a cause of a number of timeouts
-
-    >>> naked_lifeless = removeSecurityProxy(lifeless)
-    >>> naked_lifeless._inTeam_cache[vcs_imports.id]
-    True
-
-    >>> naked_lifeless._inTeam_cache[vcs_imports.id] = False
-    >>> lifeless.inTeam(vcs_imports)
-    False
-
-    >>> naked_lifeless._inTeam_cache[vcs_imports.id] = True
-    >>> lifeless.inTeam(vcs_imports)
-    True
-
-IPerson has a method to clear it; this is used between IPerson instances
-to ensure that the cache can be kept consistent when membership changes.
-
-    >>> naked_lifeless.clearInTeamCache()
-    >>> naked_lifeless._inTeam_cache
-    {}
-
-** See lib/canonical/launchpad/doc/teammembership.txt for more
-information about team membership/participation.
-
 
 Email notifications to teams
 ............................

=== modified file 'lib/lp/registry/doc/teammembership.txt'
--- lib/lp/registry/doc/teammembership.txt	2010-08-23 08:46:04 +0000
+++ lib/lp/registry/doc/teammembership.txt	2010-09-08 22:31:05 +0000
@@ -942,43 +942,6 @@
     t4
 
 
-== Membership caches ==
-
-Person instances have membership caches (_inTeam_cache) to avoid the
-need to reissue identical membership queries repeatedly. (This test uses
-Person directly to allow us access to an unproxied object, since
-_inTeam_cache is private, and it uses TeamMembershipSet directly to allow
-us to call TeamMembership.setStatus, which is private).
-
-    >>> from lp.registry.model.person import Person
-    >>> from lp.registry.model.teammembership import TeamMembershipSet
-    >>> no_priv = Person.selectOneBy(name='no-priv')
-    >>> no_priv.inTeam(admins)
-    False
-    >>> no_priv._inTeam_cache
-    {...25: False...}
-
-This cache is cleared when memberships are created:
-
-    >>> membership = TeamMembershipSet().new(
-    ...     no_priv, admins, TeamMembershipStatus.APPROVED, admins.teamowner)
-    >>> no_priv._inTeam_cache
-    {}
-    >>> no_priv.inTeam(admins)
-    True
-    >>> no_priv._inTeam_cache
-    {25: True}
-
-Or changed:
-
-    >>> membership.setStatus(TeamMembershipStatus.DEACTIVATED, mark)
-    True
-    >>> no_priv._inTeam_cache
-    {}
-    >>> no_priv.inTeam(admins)
-    False
-
-
 == Email to invalid users ==
 
 There are cases where teams have deactivated or suspended users as members.

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2010-09-01 09:57:15 +0000
+++ lib/lp/registry/model/person.py	2010-09-08 22:31:05 +0000
@@ -1240,6 +1240,11 @@
         if isinstance(team, (str, unicode)):
             team = PersonSet().getByName(team)
 
+        if not team.is_team:
+            # It is possible that this team is really a user since teams
+            # are users are often interchangable.
+            return False
+
         if self._inTeam_cache is None: # Initialize cache
             self._inTeam_cache = {}
         else:
@@ -1613,13 +1618,16 @@
                 person_table.accountID == account_table.id,
                 account_table.status == AccountStatus.ACTIVE)))
         columns.append(account_table)
+
         def handleemail(person, column):
             #-- preferred email caching
             if not person:
                 return
             email = column
             IPropertyCache(person).preferredemail = email
+
         decorators.append(handleemail)
+
         def handleaccount(person, column):
             #-- validity caching
             if not person:

=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py	2010-08-22 03:09:51 +0000
+++ lib/lp/registry/tests/test_person.py	2010-09-08 22:31:05 +0000
@@ -116,7 +116,7 @@
             in memberships]
         self.assertEqual(expected_memberships, memberships)
 
-    def test_getPathsToTeamsComplicated(self):
+    def test_getPathsToTeams_complicated(self):
         d_team = self.factory.makeTeam(name='d', owner=self.b_team)
         e_team = self.factory.makeTeam(name='e')
         f_team = self.factory.makeTeam(name='f', owner=e_team)
@@ -146,7 +146,7 @@
             in memberships]
         self.assertEqual(expected_memberships, memberships)
 
-    def test_getPathsToTeamsMultiplePaths(self):
+    def test_getPathsToTeams_multiple_paths(self):
         d_team = self.factory.makeTeam(name='d', owner=self.b_team)
         login_person(self.a_team.teamowner)
         self.c_team.addMember(d_team, self.c_team.teamowner)
@@ -168,6 +168,38 @@
             in memberships]
         self.assertEqual(expected_memberships, memberships)
 
+    def test_inTeam_direct_team(self):
+        # Verify direct membeship is True and the cache is populated.
+        self.assertTrue(self.user.inTeam(self.a_team))
+        self.assertEqual(
+            {self.a_team.id: True},
+            removeSecurityProxy(self.user)._inTeam_cache)
+
+    def test_inTeam_indirect_team(self):
+        # Verify indirect membeship is True and the cache is populated.
+        self.assertTrue(self.user.inTeam(self.b_team))
+        self.assertEqual(
+            {self.b_team.id: True},
+            removeSecurityProxy(self.user)._inTeam_cache)
+
+    def test_inTeam_cache_cleared_by_membership_change(self):
+        # Verify a change in membership clears the team cache.
+        self.user.inTeam(self.a_team)
+        with person_logged_in(self.b_team.teamowner):
+            self.b_team.addMember(self.user, self.b_team.teamowner)
+        self.assertEqual(
+            {},
+            removeSecurityProxy(self.user)._inTeam_cache)
+
+    def test_inTeam_person(self):
+        # Verify a user cannot be a member of another user and the and
+        # DB call to TeamParticipation was not made to learn this.
+        other_user = self.factory.makePerson()
+        self.assertFalse(self.user.inTeam(other_user))
+        self.assertEqual(
+            {},
+            removeSecurityProxy(self.user)._inTeam_cache)
+
 
 class TestPerson(TestCaseWithFactory):
 


Follow ups