Skip to content

Commit

Permalink
Make CveRecordValidationError picklable
Browse files Browse the repository at this point in the history
This prevents PicklingError errors when the exception is stored e.g. by
Celery when creating TaskResult records:

```
{"exc_type": "MaybeEncodingError", "exc_message":
["'PicklingError(\"Can\\'t pickle <class
\\'cvelib.cve_api.CveRecordValidationError\\'>: it\\'s not the same
object as cvelib.cve_api.CveRecordValidationError\")'", "\"(1,
<ExceptionInfo: CveRecordValidationError('Schema validation against
/usr/local/lib/python3.9/site-packages/cvelib/schemas/published_cna_container_5.0.0.json
failed')>, None)\""], "exc_module": "billiard.pool"}
```
  • Loading branch information
mprpic committed Feb 10, 2023
1 parent 4867865 commit 3c881d1
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 3 deletions.
10 changes: 7 additions & 3 deletions cvelib/cve_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ def __str__(self) -> str:


class CveRecordValidationError(Exception):
def __init__(self, message, errors):
super().__init__(message)
def __init__(self, *args, **kwargs):
errors = kwargs.pop("errors", None)
self.errors = errors
super().__init__(*args, **kwargs)

def __reduce__(self):
return CveRecordValidationError, (self.args,)


class CveRecord:
Expand Down Expand Up @@ -54,7 +58,7 @@ def validate(cls, cve_json: dict, schema_path: Optional[str] = None) -> None:
if errors:
errors_str = "\n".join(e.message for e in errors)
raise CveRecordValidationError(
f"Schema validation against {schema_path} failed:\n{errors_str}", errors
f"Schema validation against {schema_path} failed:\n{errors_str}", errors=errors
)


Expand Down
15 changes: 15 additions & 0 deletions tests/test_cve_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import pickle
from pathlib import Path

import pytest
Expand Down Expand Up @@ -38,3 +39,17 @@ def test_invalid_record_schema_validation():
exc_errors = exc_info._excinfo[1].errors
assert "'providerMetadata' is a required property" in exc_errors[0].message
assert "'rejectedReasons' is a required property" in exc_errors[1].message


def test_cve_record_validation_error_is_picklable():
try:
CveRecord.validate({})
except CveRecordValidationError as exc:
assert "'affected' is a required property" in str(exc)
assert "'affected' is a required property" == exc.errors[0].message
pickled = pickle.dumps(exc)
unpickled_exc = pickle.loads(pickled)
assert "'affected' is a required property" in str(unpickled_exc)
# Errors do not survive pickling because they include jsonschema-specific objects that
# are not picklable.
assert unpickled_exc.errors is None

0 comments on commit 3c881d1

Please sign in to comment.