Skip to content

Commit

Permalink
feat(api)!: dependency-sources
Browse files Browse the repository at this point in the history
Instead of having a relationship with versions directly projects will now
have a relation to multiple dependency sources, a dependency source is
to group dependencies to their respective lockfile, additionally
maintainers are now responsable for dependency sources instead of entire
projects.
  • Loading branch information
c0rydoras committed Mar 12, 2024
1 parent 83e9a78 commit d374384
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 121 deletions.
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 @@ -21,6 +21,7 @@


register(factories.DependencyFactory)
register(factories.DependencySourceFactory)
register(factories.VersionFactory)
register(factories.ReleaseVersionFactory)
register(factories.ProjectFactory)
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")},
),
]
25 changes: 18 additions & 7 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,34 @@ class Meta:

@property
def status(self) -> str:
first = self.versioned_dependencies.first()
first = self.sources.all().values_list("versions", flat=True).first()
return first.release_version.status if 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 @@ -133,10 +134,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 @@ -171,8 +170,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
28 changes: 21 additions & 7 deletions api/outdated/outdated/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,35 @@ class Meta:
class MaintainerSerializer(serializers.ModelSerializer):
included_serializers = {
"user": "outdated.user.serializers.UserSerializer",
"project": "outdated.outdated.serializers.ProjectSerializer",
"source": "outdated.outdated.serializers.DependencySourceSerializer",
}

class Meta:
model = models.Maintainer
fields = "__all__"


class ProjectSerializer(serializers.ModelSerializer):
class DependencySourceSerializer(serializers.ModelSerializer):
maintainers = serializers.ResourceRelatedField(
many=True,
read_only=True,
required=False,
)

included_serializers = {
"project": "outdated.outdated.serializers.ProjectSerializer",
"versions": VersionSerializer,
"maintainers": MaintainerSerializer,
}

class Meta:
model = models.DependencySource
fields = "__all__"


class ProjectSerializer(serializers.ModelSerializer):
sources = serializers.ResourceRelatedField(
many=True,
read_only=True,
)

access_token = serializers.CharField(
Expand All @@ -76,8 +92,7 @@ class ProjectSerializer(serializers.ModelSerializer):
)

included_serializers = {
"versioned_dependencies": "outdated.outdated.serializers.VersionSerializer",
"maintainers": "outdated.outdated.serializers.MaintainerSerializer",
"sources": DependencySourceSerializer,
}

class Meta:
Expand All @@ -93,8 +108,7 @@ class Meta:
"repo_type",
"access_token",
"status",
"versioned_dependencies",
"maintainers",
"sources",
)

def create(self, validated_data: dict) -> models.Project:
Expand Down
Loading

0 comments on commit d374384

Please sign in to comment.