diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 61860599..48ebd455 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -1649,6 +1649,7 @@
positionadjusted
posixpath
posixsubprocess
+ posonlyargs
postinit
postinited
postrun
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index ad984bf8..197c076e 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -738,6 +738,7 @@
positivex
positivey
positivez
+ posonlyargs
postinit
postrun
powerup
diff --git a/docs/ba_module.md b/docs/ba_module.md
index 51641da7..602bcd0c 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-
last updated on 2021-05-25 for Ballistica version 1.6.4 build 20369
+last updated on 2021-05-26 for Ballistica version 1.6.4 build 20369
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 let me know. Happy modding!
diff --git a/tests/test_efro/test_dataclassio.py b/tests/test_efro/test_dataclassio.py
index f2e00cbd..5af21aa9 100644
--- a/tests/test_efro/test_dataclassio.py
+++ b/tests/test_efro/test_dataclassio.py
@@ -623,3 +623,31 @@ def test_dict() -> None:
obj4.dval = {_GoodEnum.VAL1: 999} # type: ignore
with pytest.raises(TypeError):
dataclass_to_dict(obj4)
+
+
+def test_name_clashes() -> None:
+ """Make sure we catch name clashes since we can remap attr names."""
+
+ with pytest.raises(TypeError):
+
+ @ioprepped
+ @dataclass
+ class _TestClass:
+ ival: Annotated[int, IOAttrs('i')] = 4
+ ival2: Annotated[int, IOAttrs('i')] = 5
+
+ with pytest.raises(TypeError):
+
+ @ioprepped
+ @dataclass
+ class _TestClass2:
+ ival: int = 4
+ ival2: Annotated[int, IOAttrs('ival')] = 5
+
+ with pytest.raises(TypeError):
+
+ @ioprepped
+ @dataclass
+ class _TestClass3:
+ ival: Annotated[int, IOAttrs(store_default=False)] = 4
+ ival2: Annotated[int, IOAttrs('ival')] = 5
diff --git a/tools/efro/dataclassio.py b/tools/efro/dataclassio.py
index 320f49ad..6cd8e76d 100644
--- a/tools/efro/dataclassio.py
+++ b/tools/efro/dataclassio.py
@@ -38,7 +38,7 @@ except ModuleNotFoundError:
_pytz_utc = None # pylint: disable=invalid-name
if TYPE_CHECKING:
- from typing import Any, Dict, Type, Tuple, Optional, List
+ from typing import Any, Dict, Type, Tuple, Optional, List, Set
T = TypeVar('T')
@@ -58,8 +58,14 @@ EXTRA_ATTRS_ATTR = '_DCIOEXATTRS'
class Codec(Enum):
- """Influences format used for input/output."""
+ """Specifies expected data format exported to or imported from."""
+
+ # Use only types that will translate cleanly to/from json: lists,
+ # dicts with str keys, bools, ints, floats, and None.
JSON = 'json'
+
+ # Mostly like JSON but passes bytes and datetime objects through
+ # as-is instead of converting them to json-friendly types.
FIRESTORE = 'firestore'
@@ -267,8 +273,8 @@ class PrepSession:
'efro.dataclassio: implicitly prepping dataclass: %s.'
' It is highly recommended to explicitly prep dataclasses'
' as soon as possible after definition (via'
- ' efro.dataclassio.dataclass_prep() or the'
- ' @efro.dataclassio.prepped decorator).', cls)
+ ' efro.dataclassio.ioprep() or the'
+ ' @efro.dataclassio.ioprepped decorator).', cls)
try:
# NOTE: perhaps we want to expose the globalns/localns args
@@ -287,6 +293,7 @@ class PrepSession:
fields = dataclasses.fields(cls)
fields_by_name = {f.name: f for f in fields}
+ all_storage_names: Set[str] = set()
storage_names_to_attr_names: Dict[str, str] = {}
# Ok; we've resolved actual types for this dataclass.
@@ -301,7 +308,18 @@ class PrepSession:
if ioattrs is not None:
ioattrs.validate_for_field(cls, fields_by_name[attrname])
if ioattrs.storagename is not None:
+ storagename = ioattrs.storagename
storage_names_to_attr_names[ioattrs.storagename] = attrname
+ else:
+ storagename = attrname
+ else:
+ storagename = attrname
+
+ # Make sure we don't have any clashes in our storage names.
+ if storagename in all_storage_names:
+ raise TypeError(f'Multiple attrs on {cls} are using'
+ f' storage-name \'{storagename}\'')
+ all_storage_names.add(storagename)
self.prep_type(cls,
attrname,
diff --git a/tools/efrotools/pylintplugins.py b/tools/efrotools/pylintplugins.py
index 16ff7e2f..4b6c3c0c 100644
--- a/tools/efrotools/pylintplugins.py
+++ b/tools/efrotools/pylintplugins.py
@@ -140,6 +140,7 @@ def func_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
node.args.varargannotation = None
node.args.kwargannotation = None
node.args.kwonlyargs_annotations = [None for _ in node.args.kwonlyargs]
+ node.args.posonlyargs_annotations = [None for _ in node.args.kwonlyargs]
# Wipe out return-value annotation.
if node.returns is not None: