Skip to content

Commit b7a9871

Browse files
authored
Merge pull request #1145 from javihernandez/bs-473
Refactor package_info endpoint
2 parents 7787260 + 31624f8 commit b7a9871

File tree

9 files changed

+292
-105
lines changed

9 files changed

+292
-105
lines changed

alws/crud/package_info.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import datetime
2+
from collections import namedtuple
3+
from typing import Optional
4+
5+
from sqlalchemy import select
6+
from sqlalchemy.ext.asyncio import AsyncSession
7+
8+
from alws.errors import PlatformNotFoundError, RepositoriesNotFoundError
9+
from alws.models import Platform, Repository
10+
from alws.pulp_models import CoreRepositoryContent, RpmPackage
11+
from alws.utils.pulp_utils import get_uuid_from_pulp_href
12+
13+
14+
async def get_package_info(
15+
bs_db: AsyncSession,
16+
pulp_db: AsyncSession,
17+
package_name: str,
18+
platform_name: str,
19+
arch: Optional[str] = None,
20+
updated_after: Optional[str] = None,
21+
):
22+
platform_id_query = select(Platform.id).where(
23+
Platform.name == platform_name
24+
)
25+
platform_id = (await bs_db.execute(platform_id_query)).scalar()
26+
27+
if not platform_id:
28+
raise PlatformNotFoundError(f"Invalid distribution: {platform_name}")
29+
30+
conditions = [
31+
Repository.platform_id == platform_id,
32+
Repository.production == True,
33+
]
34+
if arch:
35+
conditions.append(Repository.arch == arch)
36+
37+
repo_query = select(Repository).where(*conditions)
38+
repositories = (await bs_db.execute(repo_query)).scalars().all()
39+
40+
if not repositories:
41+
msg = f"No repositories found for {platform_name}.{arch}"
42+
raise RepositoriesNotFoundError(msg)
43+
44+
repo_ids = [
45+
get_uuid_from_pulp_href(repo.pulp_href) for repo in repositories
46+
]
47+
48+
subq_conditions = [
49+
CoreRepositoryContent.repository_id.in_(repo_ids),
50+
CoreRepositoryContent.version_removed_id.is_(None),
51+
]
52+
if updated_after:
53+
last_updated = datetime.datetime.strptime(
54+
updated_after, "%Y-%m-%d %H:%M:%S"
55+
)
56+
subq_conditions.append(
57+
CoreRepositoryContent.pulp_last_updated >= last_updated
58+
)
59+
subq = select(CoreRepositoryContent.content_id).where(*subq_conditions)
60+
61+
query = select(
62+
RpmPackage.name,
63+
RpmPackage.version,
64+
RpmPackage.release,
65+
RpmPackage.changelogs,
66+
).where(
67+
RpmPackage.name == package_name, RpmPackage.content_ptr_id.in_(subq)
68+
)
69+
70+
pulp_packages = (await pulp_db.execute(query)).all()
71+
72+
PackageTuple = namedtuple(
73+
"PackageTuple",
74+
[
75+
"name",
76+
"version",
77+
"release",
78+
"changelogs",
79+
],
80+
)
81+
return [PackageTuple(*pulp_pkg)._asdict() for pulp_pkg in pulp_packages]

alws/errors.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,11 @@ class GenKeyError(Exception):
109109

110110
class PlatformMissingError(ValueError):
111111
pass
112+
113+
114+
class PlatformNotFoundError(Exception):
115+
pass
116+
117+
118+
class RepositoriesNotFoundError(Exception):
119+
pass

alws/pulp_models.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ class UpdateCollection(PulpBase):
4747
pulp_id: Mapped[uuid.UUID] = mapped_column(
4848
UUID(as_uuid=True), primary_key=True
4949
)
50-
pulp_created: Mapped[datetime.datetime] = mapped_column(
51-
sqlalchemy.DATETIME
52-
)
50+
pulp_created: Mapped[datetime.datetime] = mapped_column(sqlalchemy.DATETIME)
5351
pulp_last_updated: Mapped[datetime.datetime] = mapped_column(
5452
sqlalchemy.DATETIME
5553
)
@@ -125,9 +123,7 @@ class UpdateReference(PulpBase):
125123
ref_id: Mapped[Optional[str]] = mapped_column(
126124
sqlalchemy.Text, nullable=True
127125
)
128-
title: Mapped[Optional[str]] = mapped_column(
129-
sqlalchemy.Text, nullable=True
130-
)
126+
title: Mapped[Optional[str]] = mapped_column(sqlalchemy.Text, nullable=True)
131127
ref_type: Mapped[str] = mapped_column(sqlalchemy.Text)
132128
update_record_id: Mapped[uuid.UUID] = mapped_column(
133129
UUID(as_uuid=True),
@@ -404,7 +400,7 @@ class RpmPackage(PulpBase):
404400
summary: Mapped[str] = mapped_column(sqlalchemy.Text)
405401
description: Mapped[str] = mapped_column(sqlalchemy.Text)
406402
url: Mapped[str] = mapped_column(sqlalchemy.Text)
407-
# changelogs = mapped_column(JSONB)
403+
changelogs: Mapped[Dict[str, Any]] = mapped_column(JSONB)
408404
# files = mapped_column(JSONB)
409405
# requires = mapped_column(JSONB)
410406
# provides = mapped_column(JSONB)

alws/routers/package_info.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from typing import List, Optional
22

3-
from fastapi import APIRouter, Depends
3+
from fastapi import APIRouter, Depends, HTTPException, status
44
from fastapi_limiter.depends import RateLimiter
5+
from fastapi_sqla import AsyncSessionDependency
6+
from sqlalchemy.ext.asyncio import AsyncSession
57

6-
from alws.config import settings
7-
from alws.schemas import package_info_shema
8-
from alws.utils.pulp_client import PulpClient
8+
from alws.crud import package_info
9+
from alws.dependencies import get_async_db_key
10+
from alws.errors import PlatformNotFoundError, RepositoriesNotFoundError
11+
from alws.schemas import package_info_schema
912

1013
public_router = APIRouter(
1114
prefix='/package_info',
@@ -16,28 +19,39 @@
1619
@public_router.get(
1720
'/',
1821
dependencies=[Depends(RateLimiter(times=5, seconds=1))],
19-
response_model=List[package_info_shema.PackageInfo],
22+
response_model=List[package_info_schema.PackageInfo],
2023
)
2124
async def get_package_info(
22-
package_name: str,
23-
release_version: Optional[int] = None,
25+
name: str,
26+
almalinux_version: int,
27+
arch: Optional[str] = None,
28+
updated_after: Optional[str] = None,
29+
bs_db: AsyncSession = Depends(
30+
AsyncSessionDependency(key=get_async_db_key())
31+
),
32+
pulp_db: AsyncSession = Depends(AsyncSessionDependency(key='pulp_async')),
2433
):
25-
pulp_client = PulpClient(
26-
settings.pulp_host, settings.pulp_user, settings.pulp_password
27-
)
28-
packages = await pulp_client.get_rpm_packages(
29-
include_fields=[
30-
"name",
31-
"version",
32-
"release",
33-
"arch",
34-
"changelogs",
35-
],
36-
name=package_name,
37-
)
38-
if release_version is not None:
39-
release_str = f".el{release_version}"
40-
packages = [
41-
package for package in packages if release_str in package['release']
42-
]
43-
return packages
34+
"""
35+
Get information about packages from the AlmaLinux production repositories.
36+
"""
37+
platform_name = f'AlmaLinux-{almalinux_version}'
38+
try:
39+
packages = await package_info.get_package_info(
40+
bs_db,
41+
pulp_db,
42+
name,
43+
platform_name,
44+
arch,
45+
updated_after,
46+
)
47+
return packages
48+
except (PlatformNotFoundError, RepositoriesNotFoundError) as exc:
49+
raise HTTPException(
50+
detail=str(exc),
51+
status_code=status.HTTP_400_BAD_REQUEST,
52+
) from exc
53+
except Exception as exc:
54+
raise HTTPException(
55+
detail=str(exc),
56+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
57+
) from exc
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class PackageInfo(BaseModel):
99
name: str
1010
version: str
1111
release: str
12-
arch: str
1312
changelogs: List
1413

1514
class Config:

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
"tests.fixtures.database",
77
"tests.fixtures.dramatiq",
88
"tests.fixtures.errata",
9-
'tests.fixtures.limiter',
9+
"tests.fixtures.limiter",
1010
"tests.fixtures.modularity",
11+
"tests.fixtures.package_info",
1112
"tests.fixtures.platforms",
1213
"tests.fixtures.products",
1314
"tests.fixtures.pulp",

tests/fixtures/package_info.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import datetime
2+
from typing import Any, Dict, List
3+
4+
import pytest
5+
6+
from alws.errors import PlatformNotFoundError, RepositoriesNotFoundError
7+
8+
9+
@pytest.fixture
10+
def package_info() -> List[Dict[str, Any]]:
11+
return [
12+
{
13+
"name": "example_package",
14+
"version": "1.0",
15+
"release": "1.el8",
16+
"changelogs": ["Initial release"],
17+
},
18+
{
19+
"name": "example_package",
20+
"version": "1.1",
21+
"release": "2.el9",
22+
"changelogs": ["Bug fixes"],
23+
},
24+
]
25+
26+
27+
@pytest.fixture
28+
def mock_get_package_info_success(monkeypatch, package_info):
29+
async def mock_func(*args, **kwargs):
30+
return package_info
31+
32+
monkeypatch.setattr("alws.crud.package_info.get_package_info", mock_func)
33+
34+
35+
@pytest.fixture
36+
def mock_get_package_info_platform_not_found(monkeypatch):
37+
async def mock_func(*args, **kwargs):
38+
raise PlatformNotFoundError("Invalid distribution: AlmaLinux-999")
39+
40+
monkeypatch.setattr("alws.crud.package_info.get_package_info", mock_func)
41+
42+
43+
@pytest.fixture
44+
def mock_get_package_info_repos_not_found(monkeypatch):
45+
async def mock_func(*args, **kwargs):
46+
raise RepositoriesNotFoundError("No repositories found")
47+
48+
monkeypatch.setattr("alws.crud.package_info.get_package_info", mock_func)
49+
50+
51+
@pytest.fixture
52+
def mock_get_package_info_empty(monkeypatch):
53+
async def mock_func(*args, **kwargs):
54+
return []
55+
56+
monkeypatch.setattr("alws.crud.package_info.get_package_info", mock_func)
57+
58+
59+
@pytest.fixture
60+
def mock_get_package_info_with_date_filter(monkeypatch):
61+
async def mock_func(*args, **kwargs):
62+
updated_after = args[-1]
63+
packages = [
64+
{
65+
"name": "example_package",
66+
"version": "1.0",
67+
"release": "1.el8",
68+
"changelogs": ["Initial release"],
69+
"pulp_last_updated": "2024-01-01 10:00:00",
70+
},
71+
{
72+
"name": "example_package",
73+
"version": "1.1",
74+
"release": "2.el9",
75+
"changelogs": ["Security patch"],
76+
"pulp_last_updated": "2024-06-01 12:00:00",
77+
},
78+
]
79+
80+
if updated_after:
81+
cutoff = datetime.datetime.strptime(
82+
updated_after, "%Y-%m-%d %H:%M:%S"
83+
)
84+
filtered = [
85+
{k: v for k, v in pkg.items() if k != "pulp_last_updated"}
86+
for pkg in packages
87+
if datetime.datetime.strptime(
88+
pkg["pulp_last_updated"], "%Y-%m-%d %H:%M:%S"
89+
)
90+
>= cutoff
91+
]
92+
else:
93+
filtered = [
94+
{k: v for k, v in pkg.items() if k != "pulp_last_updated"}
95+
for pkg in packages
96+
]
97+
98+
return filtered
99+
100+
monkeypatch.setattr("alws.crud.package_info.get_package_info", mock_func)

tests/fixtures/pulp.py

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,19 @@ async def func(*args, **kwargs):
263263
def create_rpm_publication(monkeypatch):
264264
async def func(*args, **kwargs):
265265
return {
266-
"pulp_href": ("/pulp/api/v3/tasks/fd754c2e-3b6c-4d69-9417-6d7f5bdf1e28/"),
266+
"pulp_href": (
267+
"/pulp/api/v3/tasks/fd754c2e-3b6c-4d69-9417-6d7f5bdf1e28/"
268+
),
267269
"pulp_created": "2023-02-16T15:06:52.836410Z",
268270
"state": "completed",
269271
"name": "pulp_file.app.tasks.publishing.publish",
270272
"logging_cid": "873348c8682944ad9cf2cc50745d5ba4",
271273
"started_at": "2023-02-16T15:06:53.072299Z",
272274
"finished_at": "2023-02-16T15:06:53.229928Z",
273275
"error": None,
274-
"worker": ("/pulp/api/v3/workers/19ae1f30-d1cb-414c-9d42-29bda565e00d/"),
276+
"worker": (
277+
"/pulp/api/v3/workers/19ae1f30-d1cb-414c-9d42-29bda565e00d/"
278+
),
275279
"parent_task": None,
276280
"child_tasks": [],
277281
"task_group": None,
@@ -483,46 +487,12 @@ async def func(*args, **kwargs):
483487
monkeypatch.setattr("alws.crud.build_node.get_module_from_pulp_db", func)
484488

485489

486-
@pytest.fixture
487-
def package_info() -> List[Dict[str, Any]]:
488-
return [
489-
{
490-
"name": "example_package",
491-
"version": "example_version",
492-
"release": "example_release.el8",
493-
"arch": "example_arch",
494-
"changelogs": ["example_changelogs"],
495-
},
496-
{
497-
"name": "example_package",
498-
"version": "example_version_2",
499-
"release": "example_release_2.el9",
500-
"arch": "example_arch_2",
501-
"changelogs": ["example_changelogs_2"],
502-
},
503-
]
504-
505-
506-
@pytest.fixture
507-
def get_rpm_packages(monkeypatch, package_info):
508-
async def func(*args, **kwargs):
509-
res = []
510-
for package in package_info:
511-
if "name" in kwargs and package["name"] != kwargs["name"]:
512-
continue
513-
if "include_fields" in kwargs:
514-
package = {field: package[field] for field in kwargs["include_fields"]}
515-
res.append(package)
516-
return res
517-
518-
monkeypatch.setattr(PulpClient, "get_rpm_packages", func)
519-
520-
521490
@pytest.fixture
522491
def get_removed_rpm_packages_from_latest_repo_version(monkeypatch):
523492
def func(*args, **kwargs):
524493
class RpmPackage:
525494
pulp_href = uuid.uuid4()
495+
526496
return [RpmPackage() for i in range(10)]
527497

528498
monkeypatch.setattr(

0 commit comments

Comments
 (0)