Skip to content

Commit 4993071

Browse files
committed
Change user PK to an UUID instead of Integer
1 parent 5772cdf commit 4993071

File tree

5 files changed

+129
-19
lines changed

5 files changed

+129
-19
lines changed

tests/unit/accounts/test_services.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13+
import uuid
14+
1315
import pretend
1416

1517
from zope.interface.verify import verifyClass
@@ -60,7 +62,7 @@ def test_find_userid_existing_user(self, db_session):
6062

6163
def test_check_password_nonexistant_user(self, db_session):
6264
service = services.DatabaseUserService(db_session)
63-
assert not service.check_password(1, None)
65+
assert not service.check_password(uuid.uuid4(), None)
6466

6567
def test_check_password_invalid(self, db_session):
6668
user = UserFactory.create()

tests/unit/accounts/test_views.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# limitations under the License.
1212

1313
import datetime
14+
import uuid
1415

1516
import freezegun
1617
import pretend
@@ -118,8 +119,9 @@ def test_post_validate_redirects(self, monkeypatch, pyramid_request,
118119

119120
new_session = {}
120121

122+
user_id = uuid.uuid4()
121123
user_service = pretend.stub(
122-
find_userid=pretend.call_recorder(lambda username: 1),
124+
find_userid=pretend.call_recorder(lambda username: user_id),
123125
update_user=pretend.call_recorder(lambda *a, **kw: None),
124126
)
125127
pyramid_request.find_service = pretend.call_recorder(
@@ -134,7 +136,7 @@ def test_post_validate_redirects(self, monkeypatch, pyramid_request,
134136
)
135137

136138
pyramid_request.set_property(
137-
lambda r: 1234 if with_user else None,
139+
lambda r: str(uuid.uuid4()) if with_user else None,
138140
name="unauthenticated_userid",
139141
)
140142

@@ -161,15 +163,15 @@ def test_post_validate_redirects(self, monkeypatch, pyramid_request,
161163

162164
assert user_service.find_userid.calls == [pretend.call("theuser")]
163165
assert user_service.update_user.calls == [
164-
pretend.call(1, last_login=now),
166+
pretend.call(user_id, last_login=now),
165167
]
166168

167169
if with_user:
168170
assert new_session == {}
169171
else:
170172
assert new_session == {"a": "b", "foo": "bar"}
171173

172-
assert remember.calls == [pretend.call(pyramid_request, 1)]
174+
assert remember.calls == [pretend.call(pyramid_request, str(user_id))]
173175
assert pyramid_request.session.invalidate.calls == [pretend.call()]
174176
assert pyramid_request.find_service.calls == [
175177
pretend.call(IUserService, context=None),

warehouse/accounts/models.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Boolean, DateTime, Integer, String,
1717
)
1818
from sqlalchemy import orm, select, sql
19+
from sqlalchemy.dialects.postgresql import UUID
1920
from sqlalchemy.orm.exc import NoResultFound
2021
from sqlalchemy.ext.hybrid import hybrid_property
2122

@@ -38,7 +39,7 @@ def __getitem__(self, username):
3839
raise KeyError from None
3940

4041

41-
class User(SitemapMixin, db.ModelBase):
42+
class User(SitemapMixin, db.Model):
4243

4344
__tablename__ = "accounts_user"
4445
__table_args__ = (
@@ -51,7 +52,6 @@ class User(SitemapMixin, db.ModelBase):
5152

5253
__repr__ = make_repr("username")
5354

54-
id = Column(Integer, primary_key=True, nullable=False)
5555
username = Column(CIText, nullable=False, unique=True)
5656
name = Column(String(length=100), nullable=False)
5757
password = Column(String(length=128), nullable=False)
@@ -104,12 +104,8 @@ class Email(db.ModelBase):
104104

105105
id = Column(Integer, primary_key=True, nullable=False)
106106
user_id = Column(
107-
Integer,
108-
ForeignKey(
109-
"accounts_user.id",
110-
deferrable=True,
111-
initially="DEFERRED",
112-
),
107+
UUID(as_uuid=True),
108+
ForeignKey("accounts_user.id", deferrable=True, initially="DEFERRED"),
113109
nullable=False,
114110
)
115111
email = Column(String(length=254), nullable=False)

warehouse/legacy/tables.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
UniqueConstraint,
2424
Boolean, Date, DateTime, Integer, LargeBinary, String, Text,
2525
)
26+
from sqlalchemy.dialects.postgresql import UUID
2627

2728
from warehouse import db
2829

@@ -34,12 +35,8 @@
3435
Column("id", Integer(), primary_key=True, nullable=False),
3536
Column(
3637
"user_id",
37-
Integer(),
38-
ForeignKey(
39-
"accounts_user.id",
40-
deferrable=True,
41-
initially="DEFERRED",
42-
),
38+
UUID(as_uuid=True),
39+
ForeignKey("accounts_user.id", deferrable=True, initially="DEFERRED"),
4340
nullable=False,
4441
),
4542
Column("key_id", CIText(), nullable=False),
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
"""
13+
Switch to a UUID based primary key for User
14+
15+
Revision ID: 8c8be2c0e69e
16+
Revises: 039f45e2dbf9
17+
Create Date: 2016-07-01 18:20:42.072664
18+
"""
19+
20+
21+
import sqlalchemy as sa
22+
23+
from alembic import op
24+
from sqlalchemy.dialects import postgresql
25+
26+
27+
revision = "8c8be2c0e69e"
28+
down_revision = "039f45e2dbf9"
29+
30+
31+
def upgrade():
32+
# Add a new column which is going to hold all of our new IDs for this table
33+
# with a temporary name until we can rename it.
34+
op.add_column(
35+
"accounts_user",
36+
sa.Column(
37+
"new_id",
38+
postgresql.UUID(as_uuid=True),
39+
server_default=sa.text("gen_random_uuid()"),
40+
nullable=False,
41+
),
42+
)
43+
44+
# Add a column to tables that refer to accounts_user so they can be updated
45+
# to refer to it.
46+
op.add_column(
47+
"accounts_email",
48+
sa.Column("new_user_id", postgresql.UUID(as_uuid=True), nullable=True),
49+
)
50+
op.add_column(
51+
"accounts_gpgkey",
52+
sa.Column("new_user_id", postgresql.UUID(as_uuid=True), nullable=True),
53+
)
54+
55+
# Update our referring tables so that their new column points to the
56+
# correct user account.
57+
op.execute(
58+
""" UPDATE accounts_email
59+
SET new_user_id = accounts_user.new_id
60+
FROM accounts_user
61+
WHERE accounts_email.user_id = accounts_user.id
62+
"""
63+
)
64+
op.execute(
65+
""" UPDATE accounts_gpgkey
66+
SET new_user_id = accounts_user.new_id
67+
FROM accounts_user
68+
WHERE accounts_gpgkey.user_id = accounts_user.id
69+
"""
70+
)
71+
72+
# Disallow any NULL values in our referring tables
73+
op.alter_column("accounts_email", "new_user_id", nullable=False)
74+
op.alter_column("accounts_gpgkey", "new_user_id", nullable=False)
75+
76+
# Delete our existing fields and move our new fields into their old places.
77+
op.drop_constraint("accounts_email_user_id_fkey", "accounts_email")
78+
op.drop_column("accounts_email", "user_id")
79+
op.alter_column("accounts_email", "new_user_id", new_column_name="user_id")
80+
81+
op.drop_constraint("accounts_gpgkey_user_id_fkey", "accounts_gpgkey")
82+
op.drop_column("accounts_gpgkey", "user_id")
83+
op.alter_column(
84+
"accounts_gpgkey", "new_user_id", new_column_name="user_id")
85+
86+
# Switch the primary key from the old to the new field, drop the old name,
87+
# and rename the new field into it's place.
88+
op.drop_constraint("accounts_user_pkey", "accounts_user")
89+
op.create_primary_key(None, "accounts_user", ["new_id"])
90+
op.drop_column("accounts_user", "id")
91+
op.alter_column("accounts_user", "new_id", new_column_name="id")
92+
93+
# Finally, Setup our foreign key constraints for our referring tables.
94+
op.create_foreign_key(
95+
None,
96+
"accounts_email",
97+
"accounts_user",
98+
["user_id"],
99+
["id"],
100+
deferrable=True,
101+
)
102+
op.create_foreign_key(
103+
None,
104+
"accounts_gpgkey",
105+
"accounts_user",
106+
["user_id"],
107+
["id"],
108+
deferrable=True,
109+
)
110+
111+
112+
def downgrade():
113+
raise RuntimeError("Order No. 227 - Ни шагу назад!")

0 commit comments

Comments
 (0)