bump pydantic to 2.1.1

This commit is contained in:
Aayush Goel
2023-08-03 17:21:46 +05:30
parent 149983dced
commit 2bed3468f6
11 changed files with 61 additions and 79 deletions

View File

@@ -3,7 +3,8 @@
## master (unreleased) ## master (unreleased)
### New Features ### New Features
- ELF: implement file import and export name extractor #1607 @Aayush-Goel-04 - ELF: implement file import and export name extractor #1607 #1608 @Aayush-Goel-04
- bump pydantic from 1.10.9 to 2.1.1 #1582 @Aayush-Goel-04
### Breaking Changes ### Breaking Changes

View File

@@ -136,8 +136,8 @@ class Feature(abc.ABC): # noqa: B024
import capa.features.freeze.features import capa.features.freeze.features
return ( return (
capa.features.freeze.features.feature_from_capa(self).json() capa.features.freeze.features.feature_from_capa(self).model_dump_json()
< capa.features.freeze.features.feature_from_capa(other).json() < capa.features.freeze.features.feature_from_capa(other).model_dump_json()
) )
def get_name_str(self) -> str: def get_name_str(self) -> str:

View File

@@ -14,7 +14,7 @@ import logging
from enum import Enum from enum import Enum
from typing import List, Tuple, Union from typing import List, Tuple, Union
from pydantic import Field, BaseModel from pydantic import Field, BaseModel, ConfigDict
import capa.helpers import capa.helpers
import capa.version import capa.version
@@ -31,8 +31,7 @@ logger = logging.getLogger(__name__)
class HashableModel(BaseModel): class HashableModel(BaseModel):
class Config: model_config = ConfigDict(frozen=True)
frozen = True
class AddressType(str, Enum): class AddressType(str, Enum):
@@ -46,7 +45,7 @@ class AddressType(str, Enum):
class Address(HashableModel): class Address(HashableModel):
type: AddressType type: AddressType
value: Union[int, Tuple[int, int], None] value: Union[int, Tuple[int, int], None] = None
@classmethod @classmethod
def from_capa(cls, a: capa.features.address.Address) -> "Address": def from_capa(cls, a: capa.features.address.Address) -> "Address":
@@ -159,9 +158,7 @@ class BasicBlockFeature(HashableModel):
basic_block: Address = Field(alias="basic block") basic_block: Address = Field(alias="basic block")
address: Address address: Address
feature: Feature feature: Feature
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
class InstructionFeature(HashableModel): class InstructionFeature(HashableModel):
@@ -194,26 +191,20 @@ class FunctionFeatures(BaseModel):
address: Address address: Address
features: Tuple[FunctionFeature, ...] features: Tuple[FunctionFeature, ...]
basic_blocks: Tuple[BasicBlockFeatures, ...] = Field(alias="basic blocks") basic_blocks: Tuple[BasicBlockFeatures, ...] = Field(alias="basic blocks")
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
class Features(BaseModel): class Features(BaseModel):
global_: Tuple[GlobalFeature, ...] = Field(alias="global") global_: Tuple[GlobalFeature, ...] = Field(alias="global")
file: Tuple[FileFeature, ...] file: Tuple[FileFeature, ...]
functions: Tuple[FunctionFeatures, ...] functions: Tuple[FunctionFeatures, ...]
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
class Extractor(BaseModel): class Extractor(BaseModel):
name: str name: str
version: str = capa.version.__version__ version: str = capa.version.__version__
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
class Freeze(BaseModel): class Freeze(BaseModel):
@@ -221,9 +212,7 @@ class Freeze(BaseModel):
base_address: Address = Field(alias="base address") base_address: Address = Field(alias="base address")
extractor: Extractor extractor: Extractor
features: Features features: Features
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> str: def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> str:
@@ -324,7 +313,7 @@ def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -
) # type: ignore ) # type: ignore
# Mypy is unable to recognise `base_address` as a argument due to alias # Mypy is unable to recognise `base_address` as a argument due to alias
return freeze.json() return freeze.model_dump_json()
def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor: def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor:

View File

@@ -8,7 +8,7 @@
import binascii import binascii
from typing import Union, Optional from typing import Union, Optional
from pydantic import Field, BaseModel from pydantic import Field, BaseModel, ConfigDict
import capa.features.file import capa.features.file
import capa.features.insn import capa.features.insn
@@ -17,9 +17,7 @@ import capa.features.basicblock
class FeatureModel(BaseModel): class FeatureModel(BaseModel):
class Config: model_config = ConfigDict(frozen=True, populate_by_name=True)
frozen = True
allow_population_by_field_name = True
def to_capa(self) -> capa.features.common.Feature: def to_capa(self) -> capa.features.common.Feature:
if isinstance(self, OSFeature): if isinstance(self, OSFeature):
@@ -213,141 +211,141 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature":
class OSFeature(FeatureModel): class OSFeature(FeatureModel):
type: str = "os" type: str = "os"
os: str os: str
description: Optional[str] description: Optional[str] = None
class ArchFeature(FeatureModel): class ArchFeature(FeatureModel):
type: str = "arch" type: str = "arch"
arch: str arch: str
description: Optional[str] description: Optional[str] = None
class FormatFeature(FeatureModel): class FormatFeature(FeatureModel):
type: str = "format" type: str = "format"
format: str format: str
description: Optional[str] description: Optional[str] = None
class MatchFeature(FeatureModel): class MatchFeature(FeatureModel):
type: str = "match" type: str = "match"
match: str match: str
description: Optional[str] description: Optional[str] = None
class CharacteristicFeature(FeatureModel): class CharacteristicFeature(FeatureModel):
type: str = "characteristic" type: str = "characteristic"
characteristic: str characteristic: str
description: Optional[str] description: Optional[str] = None
class ExportFeature(FeatureModel): class ExportFeature(FeatureModel):
type: str = "export" type: str = "export"
export: str export: str
description: Optional[str] description: Optional[str] = None
class ImportFeature(FeatureModel): class ImportFeature(FeatureModel):
type: str = "import" type: str = "import"
import_: str = Field(alias="import") import_: str = Field(alias="import")
description: Optional[str] description: Optional[str] = None
class SectionFeature(FeatureModel): class SectionFeature(FeatureModel):
type: str = "section" type: str = "section"
section: str section: str
description: Optional[str] description: Optional[str] = None
class FunctionNameFeature(FeatureModel): class FunctionNameFeature(FeatureModel):
type: str = "function name" type: str = "function name"
function_name: str = Field(alias="function name") function_name: str = Field(alias="function name")
description: Optional[str] description: Optional[str] = None
class SubstringFeature(FeatureModel): class SubstringFeature(FeatureModel):
type: str = "substring" type: str = "substring"
substring: str substring: str
description: Optional[str] description: Optional[str] = None
class RegexFeature(FeatureModel): class RegexFeature(FeatureModel):
type: str = "regex" type: str = "regex"
regex: str regex: str
description: Optional[str] description: Optional[str] = None
class StringFeature(FeatureModel): class StringFeature(FeatureModel):
type: str = "string" type: str = "string"
string: str string: str
description: Optional[str] description: Optional[str] = None
class ClassFeature(FeatureModel): class ClassFeature(FeatureModel):
type: str = "class" type: str = "class"
class_: str = Field(alias="class") class_: str = Field(alias="class")
description: Optional[str] description: Optional[str] = None
class NamespaceFeature(FeatureModel): class NamespaceFeature(FeatureModel):
type: str = "namespace" type: str = "namespace"
namespace: str namespace: str
description: Optional[str] description: Optional[str] = None
class BasicBlockFeature(FeatureModel): class BasicBlockFeature(FeatureModel):
type: str = "basic block" type: str = "basic block"
description: Optional[str] description: Optional[str] = None
class APIFeature(FeatureModel): class APIFeature(FeatureModel):
type: str = "api" type: str = "api"
api: str api: str
description: Optional[str] description: Optional[str] = None
class PropertyFeature(FeatureModel): class PropertyFeature(FeatureModel):
type: str = "property" type: str = "property"
access: Optional[str] access: Optional[str] = None
property: str property: str
description: Optional[str] description: Optional[str] = None
class NumberFeature(FeatureModel): class NumberFeature(FeatureModel):
type: str = "number" type: str = "number"
number: Union[int, float] number: Union[int, float]
description: Optional[str] description: Optional[str] = None
class BytesFeature(FeatureModel): class BytesFeature(FeatureModel):
type: str = "bytes" type: str = "bytes"
bytes: str bytes: str
description: Optional[str] description: Optional[str] = None
class OffsetFeature(FeatureModel): class OffsetFeature(FeatureModel):
type: str = "offset" type: str = "offset"
offset: int offset: int
description: Optional[str] description: Optional[str] = None
class MnemonicFeature(FeatureModel): class MnemonicFeature(FeatureModel):
type: str = "mnemonic" type: str = "mnemonic"
mnemonic: str mnemonic: str
description: Optional[str] description: Optional[str] = None
class OperandNumberFeature(FeatureModel): class OperandNumberFeature(FeatureModel):
type: str = "operand number" type: str = "operand number"
index: int index: int
operand_number: int = Field(alias="operand number") operand_number: int = Field(alias="operand number")
description: Optional[str] description: Optional[str] = None
class OperandOffsetFeature(FeatureModel): class OperandOffsetFeature(FeatureModel):
type: str = "operand offset" type: str = "operand offset"
index: int index: int
operand_offset: int = Field(alias="operand offset") operand_offset: int = Field(alias="operand offset")
description: Optional[str] description: Optional[str] = None
Feature = Union[ Feature = Union[

View File

@@ -1304,7 +1304,7 @@ class CapaExplorerForm(idaapi.PluginForm):
idaapi.info("No program analysis to save.") idaapi.info("No program analysis to save.")
return return
s = self.resdoc_cache.json().encode("utf-8") s = self.resdoc_cache.model_dump_json().encode("utf-8")
path = Path(self.ask_user_capa_json_file()) path = Path(self.ask_user_capa_json_file())
if not path.exists(): if not path.exists():

View File

@@ -11,4 +11,4 @@ from capa.engine import MatchResults
def render(meta, rules: RuleSet, capabilities: MatchResults) -> str: def render(meta, rules: RuleSet, capabilities: MatchResults) -> str:
return rd.ResultDocument.from_capa(meta, rules, capabilities).json(exclude_none=True) return rd.ResultDocument.from_capa(meta, rules, capabilities).model_dump_json(exclude_none=True)

View File

@@ -9,7 +9,7 @@ import datetime
import collections import collections
from typing import Dict, List, Tuple, Union, Optional from typing import Dict, List, Tuple, Union, Optional
from pydantic import Field, BaseModel from pydantic import Field, BaseModel, ConfigDict
import capa.rules import capa.rules
import capa.engine import capa.engine
@@ -23,14 +23,11 @@ from capa.helpers import assert_never
class FrozenModel(BaseModel): class FrozenModel(BaseModel):
class Config: model_config = ConfigDict(frozen=True, extra="forbid")
frozen = True
extra = "forbid"
class Model(BaseModel): class Model(BaseModel):
class Config: model_config = ConfigDict(extra="forbid")
extra = "forbid"
class Sample(Model): class Sample(Model):
@@ -83,7 +80,7 @@ class Analysis(Model):
class Metadata(Model): class Metadata(Model):
timestamp: datetime.datetime timestamp: datetime.datetime
version: str version: str
argv: Optional[Tuple[str, ...]] argv: Optional[Tuple[str, ...]] = None
sample: Sample sample: Sample
analysis: Analysis analysis: Analysis
@@ -105,13 +102,13 @@ class CompoundStatement(StatementModel):
class SomeStatement(StatementModel): class SomeStatement(StatementModel):
type = "some" type: str = "some"
description: Optional[str] = None description: Optional[str] = None
count: int count: int
class RangeStatement(StatementModel): class RangeStatement(StatementModel):
type = "range" type: str = "range"
description: Optional[str] = None description: Optional[str] = None
min: int min: int
max: int max: int
@@ -119,7 +116,7 @@ class RangeStatement(StatementModel):
class SubscopeStatement(StatementModel): class SubscopeStatement(StatementModel):
type = "subscope" type: str = "subscope"
description: Optional[str] = None description: Optional[str] = None
scope: capa.rules.Scope scope: capa.rules.Scope
@@ -134,7 +131,7 @@ Statement = Union[
class StatementNode(FrozenModel): class StatementNode(FrozenModel):
type = "statement" type: str = "statement"
statement: Statement statement: Statement
@@ -171,7 +168,7 @@ def statement_from_capa(node: capa.engine.Statement) -> Statement:
class FeatureNode(FrozenModel): class FeatureNode(FrozenModel):
type = "feature" type: str = "feature"
feature: frz.Feature feature: frz.Feature
@@ -500,15 +497,12 @@ class MaecMetadata(FrozenModel):
malware_family: Optional[str] = Field(None, alias="malware-family") malware_family: Optional[str] = Field(None, alias="malware-family")
malware_category: Optional[str] = Field(None, alias="malware-category") malware_category: Optional[str] = Field(None, alias="malware-category")
malware_category_ov: Optional[str] = Field(None, alias="malware-category-ov") malware_category_ov: Optional[str] = Field(None, alias="malware-category-ov")
model_config = ConfigDict(frozen=True, populate_by_name=True)
class Config:
frozen = True
allow_population_by_field_name = True
class RuleMetadata(FrozenModel): class RuleMetadata(FrozenModel):
name: str name: str
namespace: Optional[str] namespace: Optional[str] = None
authors: Tuple[str, ...] authors: Tuple[str, ...]
scope: capa.rules.Scope scope: capa.rules.Scope
attack: Tuple[AttackSpec, ...] = Field(alias="att&ck") attack: Tuple[AttackSpec, ...] = Field(alias="att&ck")
@@ -546,9 +540,7 @@ class RuleMetadata(FrozenModel):
) # type: ignore ) # type: ignore
# Mypy is unable to recognise arguments due to alias # Mypy is unable to recognise arguments due to alias
class Config: model_config = ConfigDict(frozen=True, populate_by_name=True)
frozen = True
allow_population_by_field_name = True
class RuleMatches(FrozenModel): class RuleMatches(FrozenModel):

View File

@@ -48,7 +48,7 @@ dependencies = [
"pyelftools==0.29", "pyelftools==0.29",
"dnfile==0.13.0", "dnfile==0.13.0",
"dncil==1.0.2", "dncil==1.0.2",
"pydantic==1.10.9", "pydantic==2.1.1",
"protobuf==4.23.4", "protobuf==4.23.4",
] ]
dynamic = ["version"] dynamic = ["version"]

View File

@@ -214,7 +214,9 @@ def main(argv=None):
if result["status"] == "error": if result["status"] == "error":
logger.warning(result["error"]) logger.warning(result["error"])
elif result["status"] == "ok": elif result["status"] == "ok":
results[result["path"].as_posix()] = rd.ResultDocument.parse_obj(result["ok"]).json(exclude_none=True) results[result["path"].as_posix()] = rd.ResultDocument.parse_obj(result["ok"]).model_dump_json(
exclude_none=True
)
else: else:
raise ValueError(f"unexpected status: {result['status']}") raise ValueError(f"unexpected status: {result['status']}")

View File

@@ -78,7 +78,7 @@ def main(argv=None):
rdpb.ParseFromString(pb) rdpb.ParseFromString(pb)
rd = capa.render.proto.doc_from_pb2(rdpb) rd = capa.render.proto.doc_from_pb2(rdpb)
print(rd.json(exclude_none=True, indent=2, sort_keys=True)) print(rd.model_dump_json(exclude_none=True, indent=2))
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -236,7 +236,7 @@ def test_basic_block_node_from_capa():
def assert_round_trip(rd: rdoc.ResultDocument): def assert_round_trip(rd: rdoc.ResultDocument):
one = rd one = rd
doc = one.json(exclude_none=True) doc = one.model_dump_json(exclude_none=True)
two = rdoc.ResultDocument.parse_raw(doc) two = rdoc.ResultDocument.parse_raw(doc)
# show the round trip works # show the round trip works
@@ -244,14 +244,14 @@ def assert_round_trip(rd: rdoc.ResultDocument):
# which works thanks to pydantic model equality. # which works thanks to pydantic model equality.
assert one == two assert one == two
# second by showing their json representations are the same. # second by showing their json representations are the same.
assert one.json(exclude_none=True) == two.json(exclude_none=True) assert one.model_dump_json(exclude_none=True) == two.model_dump_json(exclude_none=True)
# now show that two different versions are not equal. # now show that two different versions are not equal.
three = copy.deepcopy(two) three = copy.deepcopy(two)
three.meta.__dict__.update({"version": "0.0.0"}) three.meta.__dict__.update({"version": "0.0.0"})
assert one.meta.version != three.meta.version assert one.meta.version != three.meta.version
assert one != three assert one != three
assert one.json(exclude_none=True) != three.json(exclude_none=True) assert one.model_dump_json(exclude_none=True) != three.model_dump_json(exclude_none=True)
@pytest.mark.parametrize( @pytest.mark.parametrize(