Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: dependency-sources #299

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/manage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""

import os
import sys

Expand Down
1 change: 1 addition & 0 deletions api/outdated/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


register(factories.DependencyFactory)
register(factories.DependencySourceFactory)
register(factories.VersionFactory)
register(factories.ReleaseVersionFactory)
register(factories.ProjectFactory)
Expand Down
6 changes: 2 additions & 4 deletions api/outdated/oidc_auth/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ class Request(Protocol): # pragma: no cover
"""Request protocol for permissions."""

@property
def method(self) -> str:
...
def method(self) -> str: ...

@property
def user(self) -> None | OIDCUser:
...
def user(self) -> None | OIDCUser: ...


class ObjectPermissionIsHasPermission(BasePermission):
Expand Down
20 changes: 15 additions & 5 deletions api/outdated/outdated/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,30 @@ class ProjectFactory(DjangoModelFactory):
repo = Sequence(lambda n: "github.com/userorcompany/%s/" % n)
repo_type = "public"

class Meta:
model = models.Project


class DependencySourceFactory(DjangoModelFactory):
project = SubFactory(ProjectFactory)
path = random.choice(
["/pyproject.toml", "/api/pyproject.toml", "/ember/pnpm-lock.yaml"]
)

@post_generation
def versioned_dependencies(self, create, extracted, **kwargs):
def versions(self, create, extracted, **kwargs):
if not create:
return # pragma: no cover
if extracted:
for versioned_dependency in extracted:
self.versioned_dependencies.add(versioned_dependency)
for version in extracted:
self.versions.add(version)

class Meta:
model = models.Project
model = models.DependencySource


class MaintainerFactory(DjangoModelFactory):
project = SubFactory(ProjectFactory)
source = SubFactory(DependencySourceFactory)
user = SubFactory(UserFactory)

class Meta:
Expand Down
111 changes: 73 additions & 38 deletions api/outdated/outdated/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.6 on 2024-01-09 09:41
# Generated by Django 5.0.3 on 2024-03-12 13:54

import uuid

Expand All @@ -18,7 +18,7 @@ class Migration(migrations.Migration):

operations = [
migrations.CreateModel(
name="Dependency",
name="DependencySource",
fields=[
(
"id",
Expand All @@ -29,17 +29,39 @@ class Migration(migrations.Migration):
serialize=False,
),
),
("name", models.CharField(max_length=100)),
("path", models.CharField()),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="Project",
fields=[
(
"provider",
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.CharField(db_index=True, max_length=100)),
("repo", outdated.models.RepositoryURLField(max_length=100)),
(
"repo_type",
models.CharField(
choices=[("PIP", "PIP"), ("NPM", "NPM")], max_length=10
choices=[
("public", "public"),
("access-token", "access-token"),
],
max_length=25,
),
),
],
options={
"ordering": ["name", "id"],
"unique_together": {("name", "provider")},
},
),
migrations.CreateModel(
Expand All @@ -57,13 +79,6 @@ class Migration(migrations.Migration):
("major_version", models.IntegerField()),
("minor_version", models.IntegerField()),
("end_of_life", models.DateField(blank=True, null=True)),
(
"dependency",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="outdated.dependency",
),
),
],
options={
"ordering": [
Expand All @@ -72,7 +87,6 @@ class Migration(migrations.Migration):
"major_version",
"minor_version",
],
"unique_together": {("dependency", "major_version", "minor_version")},
},
),
migrations.CreateModel(
Expand All @@ -89,13 +103,6 @@ class Migration(migrations.Migration):
),
("patch_version", models.IntegerField()),
("release_date", models.DateField(blank=True, null=True)),
(
"release_version",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="outdated.releaseversion",
),
),
],
options={
"ordering": [
Expand All @@ -105,11 +112,10 @@ class Migration(migrations.Migration):
"release_version__minor_version",
"patch_version",
],
"unique_together": {("release_version", "patch_version")},
},
),
migrations.CreateModel(
name="Project",
name="Dependency",
fields=[
(
"id",
Expand All @@ -120,25 +126,17 @@ class Migration(migrations.Migration):
serialize=False,
),
),
("name", models.CharField(db_index=True, max_length=100)),
("repo", outdated.models.RepositoryURLField(max_length=100)),
("name", models.CharField(max_length=100)),
(
"repo_type",
"provider",
models.CharField(
choices=[
("public", "public"),
("access-token", "access-token"),
],
max_length=25,
choices=[("PIP", "PIP"), ("NPM", "NPM")], max_length=10
),
),
(
"versioned_dependencies",
models.ManyToManyField(blank=True, to="outdated.version"),
),
],
options={
"ordering": ["name", "id"],
"unique_together": {("name", "provider")},
},
),
migrations.CreateModel(
Expand All @@ -155,11 +153,11 @@ class Migration(migrations.Migration):
),
("is_primary", outdated.models.UniqueBooleanField(default=False)),
(
"project",
"source",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="maintainers",
to="outdated.project",
to="outdated.dependencysource",
),
),
(
Expand All @@ -184,8 +182,45 @@ class Migration(migrations.Migration):
name="unique_project_repo",
),
),
migrations.AddField(
model_name="dependencysource",
name="project",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="sources",
to="outdated.project",
),
),
migrations.AddField(
model_name="releaseversion",
name="dependency",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="outdated.dependency"
),
),
migrations.AddField(
model_name="version",
name="release_version",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="outdated.releaseversion",
),
),
migrations.AddField(
model_name="dependencysource",
name="versions",
field=models.ManyToManyField(blank=True, to="outdated.version"),
),
migrations.AlterUniqueTogether(
name="maintainer",
unique_together={("user", "project")},
unique_together={("user", "source")},
),
migrations.AlterUniqueTogether(
name="releaseversion",
unique_together={("dependency", "major_version", "minor_version")},
),
migrations.AlterUniqueTogether(
name="version",
unique_together={("release_version", "patch_version")},
),
]
32 changes: 24 additions & 8 deletions api/outdated/outdated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ def version(self) -> str:

class Project(UUIDModel):
name = models.CharField(max_length=100, db_index=True)

versioned_dependencies = models.ManyToManyField(Version, blank=True)
repo = RepositoryURLField(max_length=100)
repo_type = models.CharField(max_length=25, choices=REPO_TYPES)

Expand Down Expand Up @@ -208,21 +206,39 @@ class Meta:

@property
def status(self) -> str:
first = self.versioned_dependencies.first()
return first.release_version.status if first else STATUS_OPTIONS["undefined"]
if not (first := self.sources.first()):
return "UNDEFINED"
return (
first.versions.first().release_version.status
if first.versions.first()
else STATUS_OPTIONS["undefined"]
)

def __str__(self):
return self.name


class DependencySource(UUIDModel):
path = models.CharField()
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="sources"
)
versions = models.ManyToManyField(Version, blank=True)

@property
def status(self) -> str:
first = self.versions.first()
return first.release_version.status if first else STATUS_OPTIONS["undefined"]


class Maintainer(UUIDModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
project = models.ForeignKey(
Project,
source = models.ForeignKey(
DependencySource,
on_delete=models.CASCADE,
related_name="maintainers",
)
is_primary = UniqueBooleanField(default=False, together=["project"])
is_primary = UniqueBooleanField(default=False, together=["source"])

class Meta:
unique_together = ("user", "project")
unique_together = ("user", "source")
18 changes: 9 additions & 9 deletions api/outdated/outdated/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
class LockfileParser:
"""Parse a lockfile and return a list of dependencies."""

def __init__(self, lockfiles: list[Path]) -> None:
def __init__(self, project: models.Project, lockfiles: list[Path]) -> None:
self.project = project
self.lockfiles = lockfiles

def _get_provider(self, name: str) -> str:
Expand Down Expand Up @@ -135,10 +136,8 @@ def _get_release_date(self, version: models.Version) -> date:

return parse_date(release_date).date()

def parse(self) -> list[models.Version]:
"""Parse the lockfile and return a dictionary of dependencies."""
versions = []

def parse(self) -> None:
"""Parse the lockfile and create the DependencySources."""
for lockfile in self.lockfiles:
name = lockfile.name
data = lockfile.read_text()
Expand Down Expand Up @@ -173,8 +172,9 @@ def parse(self) -> list[models.Version]:
and requirements[0][0] in settings.TRACKED_DEPENDENCIES
]

versions.extend(
self._get_version(dependency, provider) for dependency in dependencies
source, _ = models.DependencySource.objects.get_or_create(
path=name, project=self.project
)
source.versions.set(
[self._get_version(dependency, provider) for dependency in dependencies]
)

return versions
Loading
Loading