-
Notifications
You must be signed in to change notification settings - Fork 0
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
Extract report logic #323
base: main
Are you sure you want to change the base?
Extract report logic #323
Changes from 12 commits
959b1e4
5424588
1854598
999bebf
61424b2
a7f3d9b
82edcf7
a053520
6cd2f03
f9f831a
a5263c7
49c56fc
fa7a860
f6c607a
db1df6c
b15fc2e
7ce94cd
0a7283d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[pytest] | ||
markers = | ||
db: marks tests as database tests. requires a database container and may be slow. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,15 @@ | ||
from typing import Generator | ||
from collections.abc import Sequence | ||
import datetime as dt | ||
from functools import cache | ||
from typing import Generator, Optional | ||
|
||
from sqlalchemy import create_engine | ||
from sqlalchemy.orm import Session, sessionmaker | ||
from sqlalchemy import create_engine, select | ||
from sqlalchemy import orm | ||
from sqlalchemy.orm import Session, joinedload, sessionmaker | ||
|
||
from mainframe.constants import mainframe_settings | ||
from mainframe.models.orm import Scan | ||
from typing import Protocol | ||
|
||
# pool_size and max_overflow are set to their default values. There is never | ||
# enough load to justify increasing them. | ||
|
@@ -21,3 +27,80 @@ def get_db() -> Generator[Session, None, None]: | |
yield session | ||
finally: | ||
session.close() | ||
|
||
|
||
class StorageProtocol(Protocol): | ||
def lookup_packages( | ||
self, name: Optional[str] = None, version: Optional[str] = None, since: Optional[dt.datetime] = None | ||
) -> Sequence[Scan]: | ||
""" | ||
Lookup information on scanned packages based on name, version, or time | ||
scanned. If multiple packages are returned, they are ordered with the most | ||
recently queued package first. | ||
|
||
Args: | ||
since: A int representing a Unix timestamp representing when to begin the search from. | ||
name: The name of the package. | ||
version: The version of the package. | ||
session: DB session. | ||
|
||
Exceptions: | ||
ValueError: Invalid parameter combination was passed. See below. | ||
|
||
Returns: | ||
Sequence of `Scan`s, representing the results of the query | ||
|
||
Only certain combinations of parameters are allowed. A query is valid if any of the following combinations are used: | ||
- `name` and `version`: Return the package with name `name` and version `version`, if it exists. | ||
- `name` and `since`: Find all packages with name `name` since `since`. | ||
- `since`: Find all packages since `since`. | ||
- `name`: Find all packages with name `name`. | ||
All other combinations are disallowed. | ||
|
||
In more formal terms, a query is valid | ||
iff `((name and not since) or (not version and since))` | ||
where a given variable name means that query parameter was passed. Equivalently, a request is invalid | ||
iff `(not (name or since) or (version and since))` | ||
""" | ||
... | ||
|
||
def mark_reported(self, *, scan: Scan, subject: str) -> None: | ||
"""Mark the given `Scan` record as reported by `subject`.""" | ||
... | ||
|
||
|
||
class DatabaseStorage(StorageProtocol): | ||
def __init__(self, sessionmaker: orm.sessionmaker[Session]): | ||
self.sessionmaker = sessionmaker | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each instance should have one session that commits changes when the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I originally thought of using one |
||
def get_session(self) -> Session: | ||
return self.sessionmaker() | ||
|
||
def lookup_packages( | ||
self, name: Optional[str] = None, version: Optional[str] = None, since: Optional[dt.datetime] = None | ||
) -> Sequence[Scan]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One suggestion to consider: expanding the parameters of this function to allow querying by package status or arbitrary filters. This would enable the function to be reused across different endpoints, not just the
What do you think? |
||
query = ( | ||
select(Scan).order_by(Scan.queued_at.desc()).options(joinedload(Scan.rules), joinedload(Scan.download_urls)) | ||
) | ||
|
||
if name: | ||
query = query.where(Scan.name == name) | ||
if version: | ||
query = query.where(Scan.version == version) | ||
if since: | ||
query = query.where(Scan.finished_at >= since) | ||
|
||
session = self.get_session() | ||
with session, session.begin(): | ||
return session.scalars(query).unique().all() | ||
|
||
def mark_reported(self, *, scan: Scan, subject: str) -> None: | ||
session = self.get_session() | ||
with session, session.begin(): | ||
scan.reported_by = subject | ||
scan.reported_at = dt.datetime.now() | ||
|
||
|
||
@cache | ||
def get_storage() -> DatabaseStorage: | ||
return DatabaseStorage(sessionmaker) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filename
exceptions.py
should be better i think