mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 07:23:37 +08:00
latest cloudtool work
This commit is contained in:
parent
6adce18a4b
commit
fb3a335fd6
9
.idea/dictionaries/ericf.xml
generated
9
.idea/dictionaries/ericf.xml
generated
@ -83,7 +83,12 @@
|
|||||||
<w>assetbundle</w>
|
<w>assetbundle</w>
|
||||||
<w>assetcache</w>
|
<w>assetcache</w>
|
||||||
<w>assetdata</w>
|
<w>assetdata</w>
|
||||||
|
<w>assetpack</w>
|
||||||
<w>assetpackage</w>
|
<w>assetpackage</w>
|
||||||
|
<w>assetpackput</w>
|
||||||
|
<w>assetpackputfinish</w>
|
||||||
|
<w>assetpackputmanifest</w>
|
||||||
|
<w>assetpackputupload</w>
|
||||||
<w>assetpath</w>
|
<w>assetpath</w>
|
||||||
<w>assettype</w>
|
<w>assettype</w>
|
||||||
<w>assettypestr</w>
|
<w>assettypestr</w>
|
||||||
@ -526,6 +531,7 @@
|
|||||||
<w>fhdr</w>
|
<w>fhdr</w>
|
||||||
<w>fieldattr</w>
|
<w>fieldattr</w>
|
||||||
<w>fieldtypes</w>
|
<w>fieldtypes</w>
|
||||||
|
<w>filebytes</w>
|
||||||
<w>filecmp</w>
|
<w>filecmp</w>
|
||||||
<w>filehash</w>
|
<w>filehash</w>
|
||||||
<w>fileinfo</w>
|
<w>fileinfo</w>
|
||||||
@ -534,6 +540,7 @@
|
|||||||
<w>filenames</w>
|
<w>filenames</w>
|
||||||
<w>filepath</w>
|
<w>filepath</w>
|
||||||
<w>fileselector</w>
|
<w>fileselector</w>
|
||||||
|
<w>filesize</w>
|
||||||
<w>filestates</w>
|
<w>filestates</w>
|
||||||
<w>filterlines</w>
|
<w>filterlines</w>
|
||||||
<w>filterpath</w>
|
<w>filterpath</w>
|
||||||
@ -1129,6 +1136,7 @@
|
|||||||
<w>packagedir</w>
|
<w>packagedir</w>
|
||||||
<w>packagedirs</w>
|
<w>packagedirs</w>
|
||||||
<w>packagename</w>
|
<w>packagename</w>
|
||||||
|
<w>packageversion</w>
|
||||||
<w>painttxtattr</w>
|
<w>painttxtattr</w>
|
||||||
<w>palmos</w>
|
<w>palmos</w>
|
||||||
<w>pandoc</w>
|
<w>pandoc</w>
|
||||||
@ -1777,6 +1785,7 @@
|
|||||||
<w>vsync</w>
|
<w>vsync</w>
|
||||||
<w>vsyncs</w>
|
<w>vsyncs</w>
|
||||||
<w>vval</w>
|
<w>vval</w>
|
||||||
|
<w>waaah</w>
|
||||||
<w>wanttype</w>
|
<w>wanttype</w>
|
||||||
<w>wasdead</w>
|
<w>wasdead</w>
|
||||||
<w>weakref</w>
|
<w>weakref</w>
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class EntityMixin:
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
d_data: Dict[str, Any] = None,
|
d_data: Dict[str, Any] = None,
|
||||||
error: bool = False) -> None:
|
error: bool = True) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if not isinstance(self, CompoundValue):
|
if not isinstance(self, CompoundValue):
|
||||||
raise RuntimeError('EntityMixin class must be combined'
|
raise RuntimeError('EntityMixin class must be combined'
|
||||||
@ -60,7 +60,7 @@ class EntityMixin:
|
|||||||
"""Resets data to default."""
|
"""Resets data to default."""
|
||||||
self.set_data({}, error=True)
|
self.set_data({}, error=True)
|
||||||
|
|
||||||
def set_data(self, data: Dict, error: bool = False) -> None:
|
def set_data(self, data: Dict, error: bool = True) -> None:
|
||||||
"""Set the data for this entity and apply all value filters to it.
|
"""Set the data for this entity and apply all value filters to it.
|
||||||
|
|
||||||
Note that it is more efficient to pass data to an Entity's constructor
|
Note that it is more efficient to pass data to an Entity's constructor
|
||||||
@ -159,7 +159,7 @@ class EntityMixin:
|
|||||||
"""
|
"""
|
||||||
return json.loads(s, cls=ExtendedJSONDecoder)
|
return json.loads(s, cls=ExtendedJSONDecoder)
|
||||||
|
|
||||||
def load_from_json_str(self, s: str, error: bool = False) -> None:
|
def load_from_json_str(self, s: str, error: bool = True) -> None:
|
||||||
"""Set the entity's data in-place from a json string.
|
"""Set the entity's data in-place from a json string.
|
||||||
|
|
||||||
The 'error' argument determines whether Exceptions will be raised
|
The 'error' argument determines whether Exceptions will be raised
|
||||||
@ -171,7 +171,7 @@ class EntityMixin:
|
|||||||
self.set_data(data, error=error)
|
self.set_data(data, error=error)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json_str(cls: Type[T], s: str, error: bool = False) -> T:
|
def from_json_str(cls: Type[T], s: str, error: bool = True) -> T:
|
||||||
"""Instantiate a new instance with provided json string.
|
"""Instantiate a new instance with provided json string.
|
||||||
|
|
||||||
The 'error' argument determines whether exceptions will be raised
|
The 'error' argument determines whether exceptions will be raised
|
||||||
|
|||||||
@ -34,7 +34,6 @@ from bafoundation.entity._support import (BaseField, BoundCompoundValue,
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Dict, Type, List, Any
|
from typing import Dict, Type, List, Any
|
||||||
from bafoundation.entity._value import TypedValue, CompoundValue
|
from bafoundation.entity._value import TypedValue, CompoundValue
|
||||||
from bafoundation.entity._support import FieldInspector
|
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
TK = TypeVar('TK')
|
TK = TypeVar('TK')
|
||||||
@ -46,7 +45,7 @@ class Field(BaseField, Generic[T]):
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
d_key: str,
|
d_key: str,
|
||||||
value: 'TypedValue[T]',
|
value: TypedValue[T],
|
||||||
store_default: bool = True) -> None:
|
store_default: bool = True) -> None:
|
||||||
super().__init__(d_key)
|
super().__init__(d_key)
|
||||||
self.d_value = value
|
self.d_value = value
|
||||||
@ -73,9 +72,17 @@ class Field(BaseField, Generic[T]):
|
|||||||
# a type instead of an instance, but we don't reflect that here yet
|
# a type instead of an instance, but we don't reflect that here yet
|
||||||
# (would need to write a mypy plugin so sub-field access works first)
|
# (would need to write a mypy plugin so sub-field access works first)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __get__(self, obj: None, cls: Any = None) -> Field[T]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
def __get__(self, obj: Any, cls: Any = None) -> T:
|
def __get__(self, obj: Any, cls: Any = None) -> T:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
def __set__(self, obj: Any, value: T) -> None:
|
def __set__(self, obj: Any, value: T) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
@ -164,7 +171,7 @@ class ListField(BaseField, Generic[T]):
|
|||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
d_key: str,
|
d_key: str,
|
||||||
value: 'TypedValue[T]',
|
value: TypedValue[T],
|
||||||
store_default: bool = True) -> None:
|
store_default: bool = True) -> None:
|
||||||
super().__init__(d_key)
|
super().__init__(d_key)
|
||||||
self.d_value = value
|
self.d_value = value
|
||||||
@ -174,9 +181,14 @@ class ListField(BaseField, Generic[T]):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def filter_input(self, data: Any, error: bool) -> Any:
|
def filter_input(self, data: Any, error: bool) -> Any:
|
||||||
|
|
||||||
|
# If we were passed a BoundListField, operate on its raw values
|
||||||
|
if isinstance(data, BoundListField):
|
||||||
|
data = data.d_data
|
||||||
|
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
if error:
|
if error:
|
||||||
raise TypeError('list value expected')
|
raise TypeError(f'list value expected; got {type(data)}')
|
||||||
logging.error('Ignoring non-list data for %s: %s', self, data)
|
logging.error('Ignoring non-list data for %s: %s', self, data)
|
||||||
data = []
|
data = []
|
||||||
for i, entry in enumerate(data):
|
for i, entry in enumerate(data):
|
||||||
@ -193,8 +205,9 @@ class ListField(BaseField, Generic[T]):
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
# Access via type gives our field; via an instance gives a bound field.
|
||||||
@overload
|
@overload
|
||||||
def __get__(self, obj: None, cls: Any = None) -> FieldInspector:
|
def __get__(self, obj: None, cls: Any = None) -> ListField[T]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -204,9 +217,18 @@ class ListField(BaseField, Generic[T]):
|
|||||||
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Allow setting via a raw value list or a bound list field
|
||||||
|
@overload
|
||||||
def __set__(self, obj: Any, value: List[T]) -> None:
|
def __set__(self, obj: Any, value: List[T]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __set__(self, obj: Any, value: BoundListField[T]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __set__(self, obj: Any, value: Any) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
def get_with_data(self, data: Any) -> Any:
|
def get_with_data(self, data: Any) -> Any:
|
||||||
return BoundListField(self, data[self.d_key])
|
return BoundListField(self, data[self.d_key])
|
||||||
|
|
||||||
@ -217,7 +239,7 @@ class DictField(BaseField, Generic[TK, T]):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
d_key: str,
|
d_key: str,
|
||||||
keytype: Type[TK],
|
keytype: Type[TK],
|
||||||
field: 'TypedValue[T]',
|
field: TypedValue[T],
|
||||||
store_default: bool = True) -> None:
|
store_default: bool = True) -> None:
|
||||||
super().__init__(d_key)
|
super().__init__(d_key)
|
||||||
self.d_value = field
|
self.d_value = field
|
||||||
@ -229,6 +251,11 @@ class DictField(BaseField, Generic[TK, T]):
|
|||||||
|
|
||||||
# noinspection DuplicatedCode
|
# noinspection DuplicatedCode
|
||||||
def filter_input(self, data: Any, error: bool) -> Any:
|
def filter_input(self, data: Any, error: bool) -> Any:
|
||||||
|
|
||||||
|
# If we were passed a BoundDictField, operate on its raw values
|
||||||
|
if isinstance(data, BoundDictField):
|
||||||
|
data = data.d_data
|
||||||
|
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
if error:
|
if error:
|
||||||
raise TypeError('dict value expected')
|
raise TypeError('dict value expected')
|
||||||
@ -252,6 +279,8 @@ class DictField(BaseField, Generic[TK, T]):
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
# Return our field if accessed via type and bound-dict-field
|
||||||
|
# if via instance.
|
||||||
@overload
|
@overload
|
||||||
def __get__(self, obj: None, cls: Any = None) -> DictField[TK, T]:
|
def __get__(self, obj: None, cls: Any = None) -> DictField[TK, T]:
|
||||||
...
|
...
|
||||||
@ -263,9 +292,18 @@ class DictField(BaseField, Generic[TK, T]):
|
|||||||
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Allow setting via matching dict values or BoundDictFields
|
||||||
|
@overload
|
||||||
def __set__(self, obj: Any, value: Dict[TK, T]) -> None:
|
def __set__(self, obj: Any, value: Dict[TK, T]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __set__(self, obj: Any, value: BoundDictField[TK, T]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __set__(self, obj: Any, value: Any) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
def get_with_data(self, data: Any) -> Any:
|
def get_with_data(self, data: Any) -> Any:
|
||||||
return BoundDictField(self._keytype, self, data[self.d_key])
|
return BoundDictField(self._keytype, self, data[self.d_key])
|
||||||
|
|
||||||
@ -290,6 +328,7 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
self._store_default = store_default
|
self._store_default = store_default
|
||||||
|
|
||||||
def filter_input(self, data: Any, error: bool) -> list:
|
def filter_input(self, data: Any, error: bool) -> list:
|
||||||
|
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
if error:
|
if error:
|
||||||
raise TypeError('list value expected')
|
raise TypeError('list value expected')
|
||||||
@ -333,18 +372,34 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
# Note:
|
# Note:
|
||||||
# When setting the list, we tell the type-checker that we accept
|
# When setting the list, we tell the type-checker that we accept
|
||||||
# a raw list of CompoundValue objects, but at runtime we actually
|
# a raw list of CompoundValue objects, but at runtime we actually
|
||||||
# deal with BoundCompoundValue objects (see note in
|
# always deal with BoundCompoundValue objects (see note in
|
||||||
# BoundCompoundListField)
|
# BoundCompoundListField)
|
||||||
|
@overload
|
||||||
def __set__(self, obj: Any, value: List[TC]) -> None:
|
def __set__(self, obj: Any, value: List[TC]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __set__(self, obj: Any, value: BoundCompoundListField[TC]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __set__(self, obj: Any, value: Any) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
def get_with_data(self, data: Any) -> Any:
|
def get_with_data(self, data: Any) -> Any:
|
||||||
assert self.d_key in data
|
assert self.d_key in data
|
||||||
return BoundCompoundListField(self, data[self.d_key])
|
return BoundCompoundListField(self, data[self.d_key])
|
||||||
|
|
||||||
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
||||||
|
|
||||||
|
# If we were passed a BoundCompoundListField,
|
||||||
|
# simply convert it to a list of BoundCompoundValue objects which
|
||||||
|
# is what we work with natively here.
|
||||||
|
if isinstance(value, BoundCompoundListField):
|
||||||
|
value = list(value)
|
||||||
|
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise TypeError('CompoundListField expected list value on set.')
|
raise TypeError(f'CompoundListField expected list value on set;'
|
||||||
|
f' got {type(value)}.')
|
||||||
|
|
||||||
# Allow assigning only from a sequence of our existing children.
|
# Allow assigning only from a sequence of our existing children.
|
||||||
# (could look into expanding this to other children if we can
|
# (could look into expanding this to other children if we can
|
||||||
@ -355,7 +410,8 @@ class CompoundListField(BaseField, Generic[TC]):
|
|||||||
or not all(i.d_value is self.d_value for i in value)):
|
or not all(i.d_value is self.d_value for i in value)):
|
||||||
raise ValueError('CompoundListField assignment must be a '
|
raise ValueError('CompoundListField assignment must be a '
|
||||||
'list containing only its existing children.')
|
'list containing only its existing children.')
|
||||||
data[self.d_key] = [i.d_data for i in value]
|
data[self.d_key] = self.filter_input([i.d_data for i in value],
|
||||||
|
error=error)
|
||||||
|
|
||||||
|
|
||||||
class CompoundDictField(BaseField, Generic[TK, TC]):
|
class CompoundDictField(BaseField, Generic[TK, TC]):
|
||||||
|
|||||||
@ -34,9 +34,9 @@ if TYPE_CHECKING:
|
|||||||
CompoundDictField)
|
CompoundDictField)
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
TK = TypeVar('TK')
|
TKey = TypeVar('TKey')
|
||||||
TC = TypeVar('TC', bound='CompoundValue')
|
TCompound = TypeVar('TCompound', bound='CompoundValue')
|
||||||
TBL = TypeVar('TBL', bound='BoundCompoundListField')
|
TBoundList = TypeVar('TBoundList', bound='BoundCompoundListField')
|
||||||
|
|
||||||
|
|
||||||
class BoundCompoundValue:
|
class BoundCompoundValue:
|
||||||
@ -49,12 +49,13 @@ class BoundCompoundValue:
|
|||||||
Dict[str, Any]]):
|
Dict[str, Any]]):
|
||||||
self.d_value: CompoundValue
|
self.d_value: CompoundValue
|
||||||
self.d_data: Union[List[Any], Dict[str, Any]]
|
self.d_data: Union[List[Any], Dict[str, Any]]
|
||||||
# need to use base setters to avoid triggering our own overrides
|
|
||||||
|
# Need to use base setters to avoid triggering our own overrides.
|
||||||
object.__setattr__(self, 'd_value', value)
|
object.__setattr__(self, 'd_value', value)
|
||||||
object.__setattr__(self, 'd_data', d_data)
|
object.__setattr__(self, 'd_data', d_data)
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> Any:
|
def __eq__(self, other: Any) -> Any:
|
||||||
# allow comparing to compound and bound-compound objects
|
# Allow comparing to compound and bound-compound objects.
|
||||||
from bafoundation.entity.util import compound_eq
|
from bafoundation.entity.util import compound_eq
|
||||||
return compound_eq(self, other)
|
return compound_eq(self, other)
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ class BoundCompoundValue:
|
|||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Any) -> None:
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
# same deal as __getattr__ basically
|
# Same deal as __getattr__ basically.
|
||||||
field = getattr(type(object.__getattribute__(self, 'd_value')), name,
|
field = getattr(type(object.__getattribute__(self, 'd_value')), name,
|
||||||
None)
|
None)
|
||||||
if isinstance(field, BaseField):
|
if isinstance(field, BaseField):
|
||||||
@ -81,10 +82,12 @@ class BoundCompoundValue:
|
|||||||
value = object.__getattribute__(self, 'd_value')
|
value = object.__getattribute__(self, 'd_value')
|
||||||
data = object.__getattribute__(self, 'd_data')
|
data = object.__getattribute__(self, 'd_data')
|
||||||
assert isinstance(data, dict)
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
# Need to clear our dict in-place since we have no
|
# Need to clear our dict in-place since we have no
|
||||||
# access to our parent which we'd need to assign an empty one.
|
# access to our parent which we'd need to assign an empty one.
|
||||||
data.clear()
|
data.clear()
|
||||||
# now fill in default data
|
|
||||||
|
# Now fill in default data.
|
||||||
value.apply_fields_to_data(data, error=True)
|
value.apply_fields_to_data(data, error=True)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@ -157,7 +160,7 @@ class BoundListField(Generic[T]):
|
|||||||
self._i = 0
|
self._i = 0
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> Any:
|
def __eq__(self, other: Any) -> Any:
|
||||||
# just convert us into a regular list and run a compare with that
|
# Just convert us into a regular list and run a compare with that.
|
||||||
flattened = [
|
flattened = [
|
||||||
self.d_field.d_value.filter_output(value) for value in self.d_data
|
self.d_field.d_value.filter_output(value) for value in self.d_data
|
||||||
]
|
]
|
||||||
@ -211,18 +214,18 @@ class BoundListField(Generic[T]):
|
|||||||
self.d_data[key] = self.d_field.d_value.filter_input(value, error=True)
|
self.d_data[key] = self.d_field.d_value.filter_input(value, error=True)
|
||||||
|
|
||||||
|
|
||||||
class BoundDictField(Generic[TK, T]):
|
class BoundDictField(Generic[TKey, T]):
|
||||||
"""DictField bound to its data; used for accessing its values."""
|
"""DictField bound to its data; used for accessing its values."""
|
||||||
|
|
||||||
def __init__(self, keytype: Type[TK], field: DictField[TK, T],
|
def __init__(self, keytype: Type[TKey], field: DictField[TKey, T],
|
||||||
d_data: Dict[TK, T]):
|
d_data: Dict[TKey, T]):
|
||||||
self._keytype = keytype
|
self._keytype = keytype
|
||||||
self.d_field = field
|
self.d_field = field
|
||||||
assert isinstance(d_data, dict)
|
assert isinstance(d_data, dict)
|
||||||
self.d_data = d_data
|
self.d_data = d_data
|
||||||
|
|
||||||
def __eq__(self, other: Any) -> Any:
|
def __eq__(self, other: Any) -> Any:
|
||||||
# just convert us into a regular dict and run a compare with that
|
# Just convert us into a regular dict and run a compare with that.
|
||||||
flattened = {
|
flattened = {
|
||||||
key: self.d_field.d_value.filter_output(value)
|
key: self.d_field.d_value.filter_output(value)
|
||||||
for key, value in self.d_data.items()
|
for key, value in self.d_data.items()
|
||||||
@ -237,7 +240,7 @@ class BoundDictField(Generic[TK, T]):
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.d_data)
|
return len(self.d_data)
|
||||||
|
|
||||||
def __getitem__(self, key: TK) -> T:
|
def __getitem__(self, key: TKey) -> T:
|
||||||
if not isinstance(key, self._keytype):
|
if not isinstance(key, self._keytype):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f'Invalid key type {type(key)}; expected {self._keytype}')
|
f'Invalid key type {type(key)}; expected {self._keytype}')
|
||||||
@ -245,7 +248,7 @@ class BoundDictField(Generic[TK, T]):
|
|||||||
typedval: T = self.d_field.d_value.filter_output(self.d_data[key])
|
typedval: T = self.d_field.d_value.filter_output(self.d_data[key])
|
||||||
return typedval
|
return typedval
|
||||||
|
|
||||||
def get(self, key: TK, default: Optional[T] = None) -> Optional[T]:
|
def get(self, key: TKey, default: Optional[T] = None) -> Optional[T]:
|
||||||
"""Get a value if present, or a default otherwise."""
|
"""Get a value if present, or a default otherwise."""
|
||||||
if not isinstance(key, self._keytype):
|
if not isinstance(key, self._keytype):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
@ -256,18 +259,18 @@ class BoundDictField(Generic[TK, T]):
|
|||||||
typedval: T = self.d_field.d_value.filter_output(self.d_data[key])
|
typedval: T = self.d_field.d_value.filter_output(self.d_data[key])
|
||||||
return typedval
|
return typedval
|
||||||
|
|
||||||
def __setitem__(self, key: TK, value: T) -> None:
|
def __setitem__(self, key: TKey, value: T) -> None:
|
||||||
if not isinstance(key, self._keytype):
|
if not isinstance(key, self._keytype):
|
||||||
raise TypeError("Expected str index.")
|
raise TypeError("Expected str index.")
|
||||||
self.d_data[key] = self.d_field.d_value.filter_input(value, error=True)
|
self.d_data[key] = self.d_field.d_value.filter_input(value, error=True)
|
||||||
|
|
||||||
def __contains__(self, key: TK) -> bool:
|
def __contains__(self, key: TKey) -> bool:
|
||||||
return key in self.d_data
|
return key in self.d_data
|
||||||
|
|
||||||
def __delitem__(self, key: TK) -> None:
|
def __delitem__(self, key: TKey) -> None:
|
||||||
del self.d_data[key]
|
del self.d_data[key]
|
||||||
|
|
||||||
def keys(self) -> List[TK]:
|
def keys(self) -> List[TKey]:
|
||||||
"""Return a list of our keys."""
|
"""Return a list of our keys."""
|
||||||
return list(self.d_data.keys())
|
return list(self.d_data.keys())
|
||||||
|
|
||||||
@ -278,16 +281,16 @@ class BoundDictField(Generic[TK, T]):
|
|||||||
for value in self.d_data.values()
|
for value in self.d_data.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
def items(self) -> List[Tuple[TK, T]]:
|
def items(self) -> List[Tuple[TKey, T]]:
|
||||||
"""Return a list of item/value pairs."""
|
"""Return a list of item/value pairs."""
|
||||||
return [(key, self.d_field.d_value.filter_output(value))
|
return [(key, self.d_field.d_value.filter_output(value))
|
||||||
for key, value in self.d_data.items()]
|
for key, value in self.d_data.items()]
|
||||||
|
|
||||||
|
|
||||||
class BoundCompoundListField(Generic[TC]):
|
class BoundCompoundListField(Generic[TCompound]):
|
||||||
"""A CompoundListField bound to its entity sub-data."""
|
"""A CompoundListField bound to its entity sub-data."""
|
||||||
|
|
||||||
def __init__(self, field: CompoundListField[TC], d_data: List[Any]):
|
def __init__(self, field: CompoundListField[TCompound], d_data: List[Any]):
|
||||||
self.d_field = field
|
self.d_field = field
|
||||||
self.d_data = d_data
|
self.d_data = d_data
|
||||||
self._i = 0
|
self._i = 0
|
||||||
@ -323,20 +326,20 @@ class BoundCompoundListField(Generic[TC]):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, key: int) -> TC:
|
def __getitem__(self, key: int) -> TCompound:
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, key: slice) -> List[TC]:
|
def __getitem__(self, key: slice) -> List[TCompound]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def __getitem__(self, key: Any) -> Any:
|
def __getitem__(self, key: Any) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
def __next__(self) -> TC:
|
def __next__(self) -> TCompound:
|
||||||
...
|
...
|
||||||
|
|
||||||
def append(self) -> TC:
|
def append(self) -> TCompound:
|
||||||
"""Append and return a new field entry to the array."""
|
"""Append and return a new field entry to the array."""
|
||||||
...
|
...
|
||||||
else:
|
else:
|
||||||
@ -366,16 +369,16 @@ class BoundCompoundListField(Generic[TC]):
|
|||||||
self.d_field.d_value.get_default_data(), error=True))
|
self.d_field.d_value.get_default_data(), error=True))
|
||||||
return BoundCompoundValue(self.d_field.d_value, self.d_data[-1])
|
return BoundCompoundValue(self.d_field.d_value, self.d_data[-1])
|
||||||
|
|
||||||
def __iter__(self: TBL) -> TBL:
|
def __iter__(self: TBoundList) -> TBoundList:
|
||||||
self._i = 0
|
self._i = 0
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class BoundCompoundDictField(Generic[TK, TC]):
|
class BoundCompoundDictField(Generic[TKey, TCompound]):
|
||||||
"""A CompoundDictField bound to its entity sub-data."""
|
"""A CompoundDictField bound to its entity sub-data."""
|
||||||
|
|
||||||
def __init__(self, field: CompoundDictField[TK, TC], d_data: Dict[Any,
|
def __init__(self, field: CompoundDictField[TKey, TCompound],
|
||||||
Any]):
|
d_data: Dict[Any, Any]):
|
||||||
self.d_field = field
|
self.d_field = field
|
||||||
self.d_data = d_data
|
self.d_data = d_data
|
||||||
|
|
||||||
@ -408,16 +411,16 @@ class BoundCompoundDictField(Generic[TK, TC]):
|
|||||||
# would not be able to make sense of)
|
# would not be able to make sense of)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
def __getitem__(self, key: TK) -> TC:
|
def __getitem__(self, key: TKey) -> TCompound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def values(self) -> List[TC]:
|
def values(self) -> List[TCompound]:
|
||||||
"""Return a list of our values."""
|
"""Return a list of our values."""
|
||||||
|
|
||||||
def items(self) -> List[Tuple[TK, TC]]:
|
def items(self) -> List[Tuple[TKey, TCompound]]:
|
||||||
"""Return key/value pairs for all dict entries."""
|
"""Return key/value pairs for all dict entries."""
|
||||||
|
|
||||||
def add(self, key: TK) -> TC:
|
def add(self, key: TKey) -> TCompound:
|
||||||
"""Add an entry into the dict, returning it.
|
"""Add an entry into the dict, returning it.
|
||||||
|
|
||||||
Any existing value is replaced."""
|
Any existing value is replaced."""
|
||||||
@ -438,14 +441,14 @@ class BoundCompoundDictField(Generic[TK, TC]):
|
|||||||
return [(key, BoundCompoundValue(self.d_field.d_value, value))
|
return [(key, BoundCompoundValue(self.d_field.d_value, value))
|
||||||
for key, value in self.d_data.items()]
|
for key, value in self.d_data.items()]
|
||||||
|
|
||||||
def add(self, key: TK) -> TC:
|
def add(self, key: TKey) -> TCompound:
|
||||||
"""Add an entry into the dict, returning it.
|
"""Add an entry into the dict, returning it.
|
||||||
|
|
||||||
Any existing value is replaced."""
|
Any existing value is replaced."""
|
||||||
if not isinstance(key, self.d_field.d_keytype):
|
if not isinstance(key, self.d_field.d_keytype):
|
||||||
raise TypeError(f'expected key type {self.d_field.d_keytype};'
|
raise TypeError(f'expected key type {self.d_field.d_keytype};'
|
||||||
f' got {type(key)}')
|
f' got {type(key)}')
|
||||||
# push the entity default into data and then let it fill in
|
# Push the entity default into data and then let it fill in
|
||||||
# any children/etc.
|
# any children/etc.
|
||||||
self.d_data[key] = (self.d_field.d_value.filter_input(
|
self.d_data[key] = (self.d_field.d_value.filter_input(
|
||||||
self.d_field.d_value.get_default_data(), error=True))
|
self.d_field.d_value.get_default_data(), error=True))
|
||||||
@ -454,12 +457,12 @@ class BoundCompoundDictField(Generic[TK, TC]):
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.d_data)
|
return len(self.d_data)
|
||||||
|
|
||||||
def __contains__(self, key: TK) -> bool:
|
def __contains__(self, key: TKey) -> bool:
|
||||||
return key in self.d_data
|
return key in self.d_data
|
||||||
|
|
||||||
def __delitem__(self, key: TK) -> None:
|
def __delitem__(self, key: TKey) -> None:
|
||||||
del self.d_data[key]
|
del self.d_data[key]
|
||||||
|
|
||||||
def keys(self) -> List[TK]:
|
def keys(self) -> List[TKey]:
|
||||||
"""Return a list of our keys."""
|
"""Return a list of our keys."""
|
||||||
return list(self.d_data.keys())
|
return list(self.d_data.keys())
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||||
<!--DOCSHASH=22f3aab06ee39a80fe0b0db52e6bef03-->
|
<!--DOCSHASH=096fe9ca51969f120799a3a00bd55cfb-->
|
||||||
<h4><em>last updated on 2019-12-14 for Ballistica version 1.5.0 build 20001</em></h4>
|
<h4><em>last updated on 2019-12-19 for Ballistica version 1.5.0 build 20001</em></h4>
|
||||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -68,10 +68,10 @@ class EntityTest(entity.Entity):
|
|||||||
enumval = entity.Field('e', entity.EnumValue(EnumTest, default=None))
|
enumval = entity.Field('e', entity.EnumValue(EnumTest, default=None))
|
||||||
enumval2 = entity.Field(
|
enumval2 = entity.Field(
|
||||||
'e2', entity.OptionalEnumValue(EnumTest, default=EnumTest.SECOND))
|
'e2', entity.OptionalEnumValue(EnumTest, default=EnumTest.SECOND))
|
||||||
compoundlist = entity.CompoundListField('l', CompoundTest())
|
|
||||||
slval = entity.ListField('sl', entity.StringValue())
|
slval = entity.ListField('sl', entity.StringValue())
|
||||||
tval2 = entity.Field('t2', entity.DateTimeValue())
|
tval2 = entity.Field('t2', entity.DateTimeValue())
|
||||||
str_int_dict = entity.DictField('sd', str, entity.IntValue())
|
str_int_dict = entity.DictField('sd', str, entity.IntValue())
|
||||||
|
compoundlist = entity.CompoundListField('l', CompoundTest())
|
||||||
tdval = entity.CompoundDictField('td', str, CompoundTest())
|
tdval = entity.CompoundDictField('td', str, CompoundTest())
|
||||||
fval2 = entity.Field('f2', entity.Float3Value())
|
fval2 = entity.Field('f2', entity.Float3Value())
|
||||||
|
|
||||||
@ -98,7 +98,18 @@ def test_entity_values() -> None:
|
|||||||
ent.fval = True
|
ent.fval = True
|
||||||
ent.fval = 1.0
|
ent.fval = 1.0
|
||||||
|
|
||||||
# Simple str/int dict field.
|
# Simple value list field.
|
||||||
|
assert not ent.slval
|
||||||
|
assert len(ent.slval) == 0
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ent.slval.append(1) # type: ignore
|
||||||
|
ent.slval.append('blah')
|
||||||
|
assert len(ent.slval) == 1
|
||||||
|
assert list(ent.slval) == ['blah']
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ent.slval = ['foo', 'bar', 1] # type: ignore
|
||||||
|
|
||||||
|
# Simple value dict field.
|
||||||
assert 'foo' not in ent.str_int_dict
|
assert 'foo' not in ent.str_int_dict
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
ent.str_int_dict[0] = 123 # type: ignore
|
ent.str_int_dict[0] = 123 # type: ignore
|
||||||
@ -108,6 +119,25 @@ def test_entity_values() -> None:
|
|||||||
assert static_type_equals(ent.str_int_dict['foo'], int)
|
assert static_type_equals(ent.str_int_dict['foo'], int)
|
||||||
assert ent.str_int_dict['foo'] == 123
|
assert ent.str_int_dict['foo'] == 123
|
||||||
|
|
||||||
|
# Waaah; this works at runtime, but it seems that we'd need
|
||||||
|
# to have BoundDictField inherit from Mapping for mypy to accept this.
|
||||||
|
# (which seems to get a bit ugly, but may be worth revisiting)
|
||||||
|
# assert dict(ent.str_int_dict) == {'foo': 123}
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyTypeHints
|
||||||
|
def test_entity_values_2() -> None:
|
||||||
|
"""Test various entity assigns for value and type correctness."""
|
||||||
|
|
||||||
|
ent = EntityTest()
|
||||||
|
|
||||||
|
# Compound value
|
||||||
|
assert static_type_equals(ent.grp, CompoundTest)
|
||||||
|
assert static_type_equals(ent.grp.isubval, int)
|
||||||
|
assert isinstance(ent.grp.isubval, int)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ent.grp.isubval = 'blah' # type: ignore
|
||||||
|
|
||||||
# Compound value inheritance.
|
# Compound value inheritance.
|
||||||
assert ent.grp2.isubval2 == 3453
|
assert ent.grp2.isubval2 == 3453
|
||||||
assert ent.grp2.isubval == 34532
|
assert ent.grp2.isubval == 34532
|
||||||
@ -141,6 +171,67 @@ def test_entity_values() -> None:
|
|||||||
assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
|
assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_copies() -> None:
|
||||||
|
"""Test copying various values between fields."""
|
||||||
|
ent1 = EntityTest()
|
||||||
|
ent2 = EntityTest()
|
||||||
|
|
||||||
|
# Copying a simple value.
|
||||||
|
ent1.ival = 334
|
||||||
|
ent2.ival = ent1.ival
|
||||||
|
assert ent2.ival == 334
|
||||||
|
|
||||||
|
# Copying a nested compound.
|
||||||
|
ent1.grp.isubval = 543
|
||||||
|
ent2.grp = ent1.grp
|
||||||
|
assert ent2.grp.isubval == 543
|
||||||
|
|
||||||
|
# Type-checker currently allows this because both are Compounds
|
||||||
|
# but should fail at runtime since their subfield arrangement differs.
|
||||||
|
# reveal_type(ent1.grp.blah)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ent2.grp = ent1.grp2
|
||||||
|
|
||||||
|
# Copying a value list.
|
||||||
|
ent1.slval = ['foo', 'bar']
|
||||||
|
assert ent1.slval == ['foo', 'bar']
|
||||||
|
ent2.slval = ent1.slval
|
||||||
|
assert ent2.slval == ['foo', 'bar']
|
||||||
|
|
||||||
|
# Copying a value dict.
|
||||||
|
ent1.str_int_dict['tval'] = 987
|
||||||
|
ent2.str_int_dict = ent1.str_int_dict
|
||||||
|
assert ent2.str_int_dict['tval'] == 987
|
||||||
|
|
||||||
|
# Copying a CompoundList
|
||||||
|
val = ent1.compoundlist.append()
|
||||||
|
val.isubval = 356
|
||||||
|
assert ent1.compoundlist[0].isubval == 356
|
||||||
|
assert len(ent1.compoundlist) == 1
|
||||||
|
assert len(ent2.compoundlist) == 0
|
||||||
|
ent2.compoundlist = ent1.compoundlist
|
||||||
|
assert ent2.compoundlist[0].isubval == 356
|
||||||
|
assert len(ent2.compoundlist) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_access_from_type() -> None:
|
||||||
|
"""Accessing fields through type objects should return the Field objs."""
|
||||||
|
|
||||||
|
ent = EntityTest()
|
||||||
|
|
||||||
|
# Accessing fields through the type should return field objects
|
||||||
|
# instead of values.
|
||||||
|
assert static_type_equals(ent.ival, int)
|
||||||
|
assert isinstance(ent.ival, int)
|
||||||
|
mypytype = 'bafoundation.entity._field.Field[builtins.int*]'
|
||||||
|
assert static_type_equals(type(ent).ival, mypytype)
|
||||||
|
assert isinstance(type(ent).ival, entity.Field)
|
||||||
|
|
||||||
|
# Accessing subtype on a nested compound field..
|
||||||
|
assert static_type_equals(type(ent).compoundlist.d_value, CompoundTest)
|
||||||
|
assert isinstance(type(ent).compoundlist.d_value, CompoundTest)
|
||||||
|
|
||||||
|
|
||||||
class EntityTestMixin(entity.EntityMixin, CompoundTest2):
|
class EntityTestMixin(entity.EntityMixin, CompoundTest2):
|
||||||
"""A test entity created from a compound using a mixin class."""
|
"""A test entity created from a compound using a mixin class."""
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ CLREND = '\033[0m' # End.
|
|||||||
|
|
||||||
CMD_LOGIN = 'login'
|
CMD_LOGIN = 'login'
|
||||||
CMD_LOGOUT = 'logout'
|
CMD_LOGOUT = 'logout'
|
||||||
CMD_PUTASSETPACK = 'putassetpack'
|
CMD_ASSETPACK = 'assetpack'
|
||||||
CMD_HELP = 'help'
|
CMD_HELP = 'help'
|
||||||
|
|
||||||
# Note to self: keep this synced with server-side logic.
|
# Note to self: keep this synced with server-side logic.
|
||||||
@ -182,7 +182,7 @@ class AssetPackage:
|
|||||||
f'Invalid asset type for {assetpath} in {indexfilename}')
|
f'Invalid asset type for {assetpath} in {indexfilename}')
|
||||||
assettype = AssetType(assettypestr)
|
assettype = AssetType(assettypestr)
|
||||||
|
|
||||||
print('Looking at asset:', assetpath, assetdata)
|
# print('Looking at asset:', assetpath, assetdata)
|
||||||
package.assets[assetpath] = Asset(package, assettype, assetpath)
|
package.assets[assetpath] = Asset(package, assettype, assetpath)
|
||||||
|
|
||||||
return package
|
return package
|
||||||
@ -198,10 +198,12 @@ class AssetPackage:
|
|||||||
def _get_asset_info(iasset: Asset) -> Tuple[Asset, Dict]:
|
def _get_asset_info(iasset: Asset) -> Tuple[Asset, Dict]:
|
||||||
sha = hashlib.sha256()
|
sha = hashlib.sha256()
|
||||||
with open(iasset.filepath, 'rb') as infile:
|
with open(iasset.filepath, 'rb') as infile:
|
||||||
sha.update(infile.read())
|
filebytes = infile.read()
|
||||||
|
filesize = len(filebytes)
|
||||||
|
sha.update(filebytes)
|
||||||
if not os.path.isfile(iasset.filepath):
|
if not os.path.isfile(iasset.filepath):
|
||||||
raise Exception(f'Asset file not found: "{iasset.filepath}"')
|
raise Exception(f'Asset file not found: "{iasset.filepath}"')
|
||||||
info_out: Dict = {'hash': sha.hexdigest()}
|
info_out: Dict = {'hash': sha.hexdigest(), 'size': filesize}
|
||||||
return iasset, info_out
|
return iasset, info_out
|
||||||
|
|
||||||
# Use all procs to hash files for extra speedy goodness.
|
# Use all procs to hash files for extra speedy goodness.
|
||||||
@ -246,8 +248,9 @@ class App:
|
|||||||
self.do_login()
|
self.do_login()
|
||||||
elif cmd == CMD_LOGOUT:
|
elif cmd == CMD_LOGOUT:
|
||||||
self.do_logout()
|
self.do_logout()
|
||||||
elif cmd == CMD_PUTASSETPACK:
|
elif (cmd == CMD_ASSETPACK and len(sys.argv) > 2
|
||||||
self.do_putassetpack()
|
and sys.argv[2] == 'put'):
|
||||||
|
self.do_assetpack_put()
|
||||||
else:
|
else:
|
||||||
# For all other commands, simply pass them to the server verbatim.
|
# For all other commands, simply pass them to the server verbatim.
|
||||||
self.do_misc_command()
|
self.do_misc_command()
|
||||||
@ -332,31 +335,37 @@ class App:
|
|||||||
self._state.login_token = None
|
self._state.login_token = None
|
||||||
print(f'{CLRGRN}Cloudtool is now logged out.{CLREND}')
|
print(f'{CLRGRN}Cloudtool is now logged out.{CLREND}')
|
||||||
|
|
||||||
def do_putassetpack(self) -> None:
|
def do_assetpack_put(self) -> None:
|
||||||
"""Run a putassetpack command."""
|
"""Run an assetpackput command."""
|
||||||
|
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 4:
|
||||||
raise CleanError('Expected a path to an assetpackage directory.')
|
raise CleanError('Expected a path to an assetpackage directory.')
|
||||||
|
|
||||||
path = Path(sys.argv[2])
|
path = Path(sys.argv[3])
|
||||||
package = AssetPackage.load_from_disk(path)
|
package = AssetPackage.load_from_disk(path)
|
||||||
|
|
||||||
# Send the server a manifest of everything we've got locally.
|
# Send the server a manifest of everything we've got locally.
|
||||||
manifest = package.get_manifest()
|
manifest = package.get_manifest()
|
||||||
print('SENDING PACKAGE MANIFEST:', manifest)
|
response = self._servercmd('assetpackputmanifest', {'m': manifest})
|
||||||
response = self._servercmd('putassetpackmanifest', {'m': manifest})
|
|
||||||
|
|
||||||
# The server should give us an upload id and a set of files it wants.
|
# The server should give us a version and a set of files it wants.
|
||||||
# Upload each of those.
|
# Upload each of those.
|
||||||
upload_files: List[str] = response.data['upload_files']
|
upload_files: List[str] = response.data['upload_files']
|
||||||
assert isinstance(upload_files, list)
|
assert isinstance(upload_files, list)
|
||||||
assert all(isinstance(f, str) for f in upload_files)
|
assert all(isinstance(f, str) for f in upload_files)
|
||||||
self._putassetpack_upload(package, upload_files)
|
version = response.data['package_version']
|
||||||
|
assert isinstance(version, str)
|
||||||
|
self._assetpack_put_upload(package, version, upload_files)
|
||||||
|
|
||||||
print('Asset upload successful!')
|
# Lastly, send a 'finish' command - this will prompt a response
|
||||||
|
# with info about the completed package.
|
||||||
|
_response = self._servercmd('assetpackputfinish', {
|
||||||
|
'packageversion': version,
|
||||||
|
})
|
||||||
|
# print(f'{CLRGRN}Created asset package: {version}{CLREND}')
|
||||||
|
|
||||||
def _putassetpack_upload(self, package: AssetPackage,
|
def _assetpack_put_upload(self, package: AssetPackage, version: str,
|
||||||
files: List[str]) -> None:
|
files: List[str]) -> None:
|
||||||
|
|
||||||
# Upload the files one at a time.
|
# Upload the files one at a time.
|
||||||
# (we can potentially do this in parallel in the future).
|
# (we can potentially do this in parallel in the future).
|
||||||
@ -374,9 +383,14 @@ class App:
|
|||||||
check=True)
|
check=True)
|
||||||
with open(gzpath, 'rb') as infile:
|
with open(gzpath, 'rb') as infile:
|
||||||
putfiles: Dict = {'file': infile}
|
putfiles: Dict = {'file': infile}
|
||||||
_response = self._servercmd('putassetpackupload',
|
_response = self._servercmd(
|
||||||
{'path': asset.path},
|
'assetpackputupload',
|
||||||
files=putfiles)
|
{
|
||||||
|
'packageversion': version,
|
||||||
|
'path': asset.path
|
||||||
|
},
|
||||||
|
files=putfiles,
|
||||||
|
)
|
||||||
|
|
||||||
def do_misc_command(self) -> None:
|
def do_misc_command(self) -> None:
|
||||||
"""Run a miscellaneous command."""
|
"""Run a miscellaneous command."""
|
||||||
|
|||||||
@ -141,11 +141,11 @@ def run(cmd: str) -> None:
|
|||||||
subprocess.run(cmd, shell=True, check=True)
|
subprocess.run(cmd, shell=True, check=True)
|
||||||
|
|
||||||
|
|
||||||
def get_files_hash(
|
# 1
|
||||||
filenames: Sequence[Union[str, Path]],
|
def get_files_hash(filenames: Sequence[Union[str, Path]],
|
||||||
extrahash: str = '',
|
extrahash: str = '',
|
||||||
int_only: bool = False,
|
int_only: bool = False,
|
||||||
hashtype: Union[Literal['md5'], Literal['sha256']] = 'md5') -> str:
|
hashtype: Literal['md5', 'sha256'] = 'md5') -> str:
|
||||||
"""Return a md5 hash for the given files."""
|
"""Return a md5 hash for the given files."""
|
||||||
import hashlib
|
import hashlib
|
||||||
if not isinstance(filenames, list):
|
if not isinstance(filenames, list):
|
||||||
|
|||||||
@ -357,10 +357,20 @@ def sync_all() -> None:
|
|||||||
This assumes that there is a 'syncfull' and 'synclist' Makefile target
|
This assumes that there is a 'syncfull' and 'synclist' Makefile target
|
||||||
under each project.
|
under each project.
|
||||||
"""
|
"""
|
||||||
|
print(f'{CLRBLU}Updating formatting for all projects...{CLREND}')
|
||||||
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
||||||
if projects_str is None:
|
if projects_str is None:
|
||||||
print('EFROTOOL_SYNC_PROJECTS is not defined.')
|
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
||||||
sys.exit(255)
|
|
||||||
|
# No matter what we're doing (even if just listing), run formatting
|
||||||
|
# in all projects before beginning. Otherwise if we do a sync and then
|
||||||
|
# a preflight we'll often wind up getting out-of-sync errors due to
|
||||||
|
# formatting changing after the sync.
|
||||||
|
for project in projects_str.split(':'):
|
||||||
|
cmd = f'cd "{project}" && make format'
|
||||||
|
print(cmd)
|
||||||
|
subprocess.run(cmd, shell=True, check=True)
|
||||||
|
|
||||||
if len(sys.argv) > 2 and sys.argv[2] == 'list':
|
if len(sys.argv) > 2 and sys.argv[2] == 'list':
|
||||||
# List mode
|
# List mode
|
||||||
for project in projects_str.split(':'):
|
for project in projects_str.split(':'):
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Type, Dict, Optional, List
|
from typing import Any, Type, Dict, Optional, List, Union
|
||||||
|
|
||||||
# Global state:
|
# Global state:
|
||||||
# We maintain a single temp dir where our mypy cache and our temp
|
# We maintain a single temp dir where our mypy cache and our temp
|
||||||
@ -131,7 +131,13 @@ class StaticTestFile:
|
|||||||
# Parse this line as AST - we should find an assert
|
# Parse this line as AST - we should find an assert
|
||||||
# statement containing a static_type_equals() call
|
# statement containing a static_type_equals() call
|
||||||
# with 2 args.
|
# with 2 args.
|
||||||
tree = ast.parse(line[offset:])
|
try:
|
||||||
|
tree = ast.parse(line[offset:])
|
||||||
|
except Exception:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self._filename} line {lineno+1}: unable to "
|
||||||
|
f"parse line (static_type_equals() call cannot"
|
||||||
|
f" be split across lines).") from None
|
||||||
assert isinstance(tree, ast.Module)
|
assert isinstance(tree, ast.Module)
|
||||||
if (len(tree.body) != 1
|
if (len(tree.body) != 1
|
||||||
or not isinstance(tree.body[0], ast.Assert)):
|
or not isinstance(tree.body[0], ast.Assert)):
|
||||||
@ -165,13 +171,19 @@ class StaticTestFile:
|
|||||||
return '\n'.join(lines_out) + '\n'
|
return '\n'.join(lines_out) + '\n'
|
||||||
|
|
||||||
|
|
||||||
def static_type_equals(value: Any, statictype: Type) -> bool:
|
def static_type_equals(value: Any, statictype: Union[Type, str]) -> bool:
|
||||||
"""Check a type statically using mypy."""
|
"""Check a type statically using mypy.
|
||||||
|
|
||||||
|
If a string is passed as statictype, it is checked against the mypy
|
||||||
|
output for an exact match.
|
||||||
|
If a type is passed, various filtering may apply when searching for
|
||||||
|
a match (for instance, if mypy outputs 'builtins.int*' it will match
|
||||||
|
the 'int' type passed in as statictype).
|
||||||
|
"""
|
||||||
from inspect import getframeinfo, stack
|
from inspect import getframeinfo, stack
|
||||||
|
|
||||||
# We don't actually use there here; we pull them as strings from the src.
|
# We don't actually use there here; we pull them as strings from the src.
|
||||||
del value
|
del value
|
||||||
del statictype
|
|
||||||
|
|
||||||
# Get the filename and line number of the calling function.
|
# Get the filename and line number of the calling function.
|
||||||
caller = getframeinfo(stack()[1][0])
|
caller = getframeinfo(stack()[1][0])
|
||||||
@ -182,23 +194,36 @@ def static_type_equals(value: Any, statictype: Type) -> bool:
|
|||||||
_statictestfiles[filename] = StaticTestFile(filename)
|
_statictestfiles[filename] = StaticTestFile(filename)
|
||||||
testfile = _statictestfiles[filename]
|
testfile = _statictestfiles[filename]
|
||||||
|
|
||||||
wanttype = testfile.linetypes_wanted[linenumber]
|
|
||||||
mypytype = testfile.linetypes_mypy[linenumber]
|
mypytype = testfile.linetypes_mypy[linenumber]
|
||||||
|
|
||||||
# Do some filtering of Mypy types so we can compare to simple python ones.
|
if isinstance(statictype, str):
|
||||||
# (ie: 'builtins.list[builtins.int*]' -> int)
|
# If they passed a string value as the statictype,
|
||||||
# Note to self: perhaps we'd want a fallback form where we can pass a
|
# do a comparison with the exact mypy value.
|
||||||
# type as a string if we want to match the exact mypy value?...
|
wanttype = statictype
|
||||||
mypytype = mypytype.replace('*', '')
|
else:
|
||||||
mypytype = mypytype.replace('?', '')
|
# If they passed a type object, things are a bit trickier because
|
||||||
mypytype = mypytype.replace('builtins.int', 'int')
|
# mypy's name for the type might not match the name we pass it in with.
|
||||||
mypytype = mypytype.replace('builtins.float', 'float')
|
# Try to do some filtering to minimize these differences...
|
||||||
mypytype = mypytype.replace('builtins.list', 'List')
|
wanttype = testfile.linetypes_wanted[linenumber]
|
||||||
mypytype = mypytype.replace('builtins.bool', 'bool')
|
del statictype
|
||||||
mypytype = mypytype.replace('typing.Sequence', 'Sequence')
|
|
||||||
|
|
||||||
# temp3.FooClass -> FooClass
|
# Do some filtering of Mypy types so we can compare
|
||||||
mypytype = mypytype.replace(testfile.modulename + '.', '')
|
# to simple python ones.
|
||||||
|
|
||||||
|
# (ie: 'builtins.list[builtins.int*]' -> int)
|
||||||
|
# Note to self: perhaps we'd want a fallback form where we can pass a
|
||||||
|
# type as a string if we want to match the exact mypy value?...
|
||||||
|
mypytype = mypytype.replace('*', '')
|
||||||
|
mypytype = mypytype.replace('?', '')
|
||||||
|
mypytype = mypytype.replace('builtins.int', 'int')
|
||||||
|
mypytype = mypytype.replace('builtins.float', 'float')
|
||||||
|
mypytype = mypytype.replace('builtins.list', 'List')
|
||||||
|
mypytype = mypytype.replace('builtins.bool', 'bool')
|
||||||
|
mypytype = mypytype.replace('typing.Sequence', 'Sequence')
|
||||||
|
|
||||||
|
# So classes declared in the test file can be passed using base names.
|
||||||
|
# ie: temp3.FooClass -> FooClass
|
||||||
|
mypytype = mypytype.replace(testfile.modulename + '.', '')
|
||||||
|
|
||||||
if wanttype != mypytype:
|
if wanttype != mypytype:
|
||||||
print(f'Mypy type "{mypytype}" does not match '
|
print(f'Mypy type "{mypytype}" does not match '
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user