# standard libs
from abc import ABC, abstractmethod
-from dataclasses import dataclass
+from dataclasses import dataclass, field as dc_field
from pathlib import Path
-from typing import Callable, Optional, Self
+from typing import Any, Callable, Optional, Self
# ourselves
from bricksplom.constants import CHAR_NEWLINE
from bricksplom.ldraw import LdrawDb, LdrawPathFailure, LdrawRefsTree
n_studs: int = -1
description: str = '?'
ldraw: str = ''
+ _secured: set = dc_field(default_factory=set)
+
+ @classmethod
+ def sortables(
+ cls
+ ) -> dict[str, Any]:
+ 'Any .__annotations__ whose key does not start with an underscore.'
+ return {k: v for k, v in cls.__annotations__.items()
+ if not k.startswith('_')}
+
+ def secure(
+ self,
+ attr_key: str
+ ) -> None:
+ 'Declare knowledge of attribute as certain (overpower ldraw guesses).'
+ self._secured.add(attr_key)
+
+ def secured(
+ self,
+ attr_key: str
+ ) -> bool:
+ 'Is knowledge of attribute certain (to owerpower ldraw guesses)?'
+ return attr_key in self._secured
class BrickDesign(Textfiled, WithDb, Lookupable):
super().__init__(**kwargs)
def __getattribute__(self, key: str):
- if key in BrickDesignData.__annotations__:
+ if key in BrickDesignData.__annotations__ or key == 'secured':
if self.direct_attrs:
attrs = self.direct_attrs
else:
return 'by ' + ATTR_DESCS[attr_name], match
return super().matchers() | {
f'{attr_name}{CHAR_ATTR_EQ}': attr_matcher(attr_name)
- for attr_name, attr_type in BrickDesignData.__annotations__.items()
+ for attr_name, attr_type in BrickDesignData.sortables().items()
if attr_type is int}
@classmethod
return super().sorters()\
| {TOK_SORT_BOX: cls._by_box_sorter('design')}\
| {attr_name: attr_sorter(attr_name)
- for attr_name in BrickDesignData.__annotations__}
+ for attr_name in BrickDesignData.sortables().keys()}
@classmethod
def from_textfile(
for attr in metadata.split(SEP_DESIGN_ATTR):
assert CHAR_ATTR_EQ in attr
a_key, a_val_str = attr.split(CHAR_ATTR_EQ, maxsplit=1)
+ if a_val_str[:1] == CHAR_ATTR_EQ:
+ a_val_str = a_val_str[1:]
+ attrs.secure((a_key))
assert a_key in annos
if annos[a_key] is int:
assert a_val_str.isdigit(), a_val_str
if self.alternate_to:
return f'{raw}{CHAR_DESIGN_ALT}{self.alternate_to.id_}'
attrs = []
- for attr_key in [k for k in BrickDesignData.__annotations__
+ for attr_key in [k for k in BrickDesignData.sortables()
if k != 'description']:
attr_val = getattr(self, attr_key)
if attr_val != getattr(BrickDesignData, attr_key):
f'{design.id_} ({design.ldraw})']
continue
# compare n_studs calculations to explicit records
- if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes:
- if design.n_studs <= -1:
- continue
+ if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes\
+ and design.n_studs > -1\
+ and not design.secured('n_studs'):
studs_counted = self.ldraw.count_studs(design.ldraw)
if design.n_studs != studs_counted:
fails += ['n_studs count differs between record and ldraw '