Skip to content

Commit

Permalink
Create Files.requires_python column (#1448)
Browse files Browse the repository at this point in the history
Denormalize Release.requires_python into File.requires_python to better enable
performant retrieval of that information when fetching a list of files.
  • Loading branch information
Carreau authored and dstufft committed Oct 5, 2016
1 parent e1b3479 commit 1400ce5
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
14 changes: 14 additions & 0 deletions tests/unit/packaging/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,20 @@ def test_urls(self, db_session, home_page, download_url, project_urls,

class TestFile:

def test_requires_python(self, db_session):
""" Attempt to write a File by setting requires_python directly,
which should fail to validate (it should only be set in Release).
"""
with pytest.raises(RuntimeError):
project = DBProjectFactory.create()
release = DBReleaseFactory.create(project=project)
DBFileFactory.create(
release=release,
filename="{}-{}.tar.gz".format(project.name, release.version),
python_version="source",
requires_python="1.0"
)

def test_compute_paths(self, db_session):
project = DBProjectFactory.create()
release = DBReleaseFactory.create(project=project)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Add a requires_python column to release_files; pursuant to enabling PEP 503.
Revision ID: be4cf6b58557
Revises: 3d2b8a42219a
Create Date: 2016-09-15 04:12:53.430363
"""

from alembic import op
import sqlalchemy as sa


revision = 'be4cf6b58557'
down_revision = '3d2b8a42219a'


def upgrade():
"""
Add column `requires_python` in the `release_files` table.
"""
op.add_column("release_files",
sa.Column("requires_python",
sa.Text(),
nullable=True)
)

# Populate the column with content from release.requires_python.
op.execute(
""" UPDATE release_files
SET requires_python = releases.requires_python
FROM releases
WHERE
release_files.name=releases.name
AND release_files.version=releases.version;
"""
)

# Setup a trigger function to ensure that requires_python value on
# releases is always canonical.
op.execute(
"""CREATE OR REPLACE FUNCTION update_release_files_requires_python()
RETURNS TRIGGER AS $$
BEGIN
UPDATE
release_files
SET
requires_python = releases.requires_python
FROM releases
WHERE
release_files.name=releases.name
AND release_files.version=releases.version
AND release_files.name = NEW.name
AND releases.version = NEW.version;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
"""
)

# Establish a trigger such that on INSERT/UPDATE on releases we update
# release_files with the appropriate requires_python values.
op.execute(
""" CREATE TRIGGER releases_requires_python
AFTER INSERT OR UPDATE OF requires_python ON releases
FOR EACH ROW
EXECUTE PROCEDURE update_release_files_requires_python();
"""
)


def downgrade():
"""
Drop trigger and function that synchronize `releases`.
"""
op.execute("DROP TRIGGER releases_requires_python ON releases")
op.execute("DROP FUNCTION update_release_files_requires_python()")
op.drop_column("release_files", "requires_python")
6 changes: 6 additions & 0 deletions warehouse/packaging/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Boolean, DateTime, Integer, Table, Text,
)
from sqlalchemy import func, orm, sql
from sqlalchemy.orm import validates
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declared_attr
Expand Down Expand Up @@ -369,6 +370,7 @@ class File(db.Model):
name = Column(Text)
version = Column(Text)
python_version = Column(Text)
requires_python = Column(Text)
packagetype = Column(
Enum(
"bdist_dmg", "bdist_dumb", "bdist_egg", "bdist_msi", "bdist_rpm",
Expand All @@ -394,6 +396,10 @@ def pgp_path(self):
def pgp_path(self):
return func.concat(self.path, ".asc")

@validates("requires_python")
def validates_requires_python(self, *args, **kwargs):
raise RuntimeError("Cannot set File.requires_python")


class Filename(db.ModelBase):

Expand Down

0 comments on commit 1400ce5

Please sign in to comment.