Skip to content

Commit

Permalink
feat!: added dependency sources to api
Browse files Browse the repository at this point in the history
  • Loading branch information
c0rydoras committed Jan 16, 2024
1 parent cbc1489 commit 301c958
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 99 deletions.
1 change: 1 addition & 0 deletions api/outdated/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
register(factories.ReleaseVersionFactory)
register(factories.ProjectFactory)
register(factories.MaintainerFactory)
register(factories.DependencySourceFactory)
register(UserFactory)


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
82 changes: 59 additions & 23 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 4.2.6 on 2024-01-12 15:45

from django.db import migrations, models
import django.db.models.deletion
Expand All @@ -25,35 +25,24 @@ class Migration(migrations.Migration):
],
options={
'ordering': ['name', 'id'],
'unique_together': {('name', 'provider')},
},
),
migrations.CreateModel(
name='ReleaseVersion',
name='DependencySource',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('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')),
('path', models.CharField()),
],
options={
'ordering': ['end_of_life', 'dependency__name', 'major_version', 'minor_version'],
'unique_together': {('dependency', 'major_version', 'minor_version')},
'abstract': False,
},
),
migrations.CreateModel(
name='Version',
name='Maintainer',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('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')),
('is_primary', outdated.models.UniqueBooleanField(default=False)),
],
options={
'ordering': ['release_version__end_of_life', 'release_version__dependency__name', 'release_version__major_version', 'release_version__minor_version', 'patch_version'],
'unique_together': {('release_version', 'patch_version')},
},
),
migrations.CreateModel(
name='Project',
Expand All @@ -62,20 +51,35 @@ class Migration(migrations.Migration):
('name', models.CharField(db_index=True, max_length=100)),
('repo', outdated.models.RepositoryURLField(max_length=100)),
('repo_type', models.CharField(choices=[('public', 'public'), ('access-token', 'access-token')], max_length=25)),
('versioned_dependencies', models.ManyToManyField(blank=True, to='outdated.version')),
],
options={
'ordering': ['name', 'id'],
},
),
migrations.CreateModel(
name='Maintainer',
name='ReleaseVersion',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('is_primary', outdated.models.UniqueBooleanField(default=False)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='maintainers', to='outdated.project')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
('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': ['end_of_life', 'dependency__name', 'major_version', 'minor_version'],
},
),
migrations.CreateModel(
name='Version',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('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': ['release_version__end_of_life', 'release_version__dependency__name', 'release_version__major_version', 'release_version__minor_version', 'patch_version'],
},
),
migrations.AddConstraint(
model_name='project',
Expand All @@ -85,8 +89,40 @@ class Migration(migrations.Migration):
model_name='project',
constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('repo'), name='unique_project_repo'),
),
migrations.AddField(
model_name='maintainer',
name='source',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='maintainers', to='outdated.dependencysource'),
),
migrations.AddField(
model_name='maintainer',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user'),
),
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='dependencysource',
name='versions',
field=models.ManyToManyField(blank=True, to='outdated.version'),
),
migrations.AlterUniqueTogether(
name='dependency',
unique_together={('name', 'provider')},
),
migrations.AlterUniqueTogether(
name='version',
unique_together={('release_version', 'patch_version')},
),
migrations.AlterUniqueTogether(
name='releaseversion',
unique_together={('dependency', 'major_version', 'minor_version')},
),
migrations.AlterUniqueTogether(
name='maintainer',
unique_together={('user', 'project')},
unique_together={('user', 'source')},
),
]
25 changes: 18 additions & 7 deletions api/outdated/outdated/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,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 @@ -202,21 +200,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")
16 changes: 8 additions & 8 deletions api/outdated/outdated/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
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.lockfiles = lockfiles
self.project = project

def _get_provider(self, name: str) -> str:
"""Get the provider of the lockfile."""
Expand Down Expand Up @@ -102,10 +103,8 @@ def _get_release_date(self, version: models.Version) -> date:

return parse_date(release_date).date()

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

for lockfile in self.lockfiles:
name = lockfile.name
data = lockfile.read_text()
Expand Down Expand Up @@ -139,8 +138,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
35 changes: 21 additions & 14 deletions api/outdated/outdated/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,44 @@ 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 = {
"versions": VersionSerializer,
"maintainers": MaintainerSerializer,
}

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


class ProjectSerializer(serializers.ModelSerializer):
access_token = serializers.CharField(
max_length=100,
write_only=True,
required=False,
allow_blank=True,
validators=[RegexValidator(r"[-_a-zA-Z\d]+")],
)

sources = serializers.ResourceRelatedField(
many=True,
read_only=True,
)

repo = serializers.CharField(
validators=[
UniqueValidator(queryset=models.Project.objects.all(), lookup="iexact")
Expand All @@ -76,8 +92,7 @@ class ProjectSerializer(serializers.ModelSerializer):
)

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

class Meta:
Expand All @@ -87,15 +102,7 @@ class Meta:
validate_access_token_required,
validate_no_access_token_when_public,
]
fields = (
"name",
"repo",
"repo_type",
"access_token",
"status",
"versioned_dependencies",
"maintainers",
)
fields = ("name", "repo", "repo_type", "access_token", "status", "sources")

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

0 comments on commit 301c958

Please sign in to comment.