tcod-ecs 5.1.0__tar.gz → 5.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tcod-ecs
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: A type-hinted Entity Component System based on Python dictionaries and sets.
5
5
  Author-email: Kyle Benesch <4b796c65+github@gmail.com>
6
6
  Requires-Python: >=3.8
@@ -236,13 +236,45 @@ True
236
236
 
237
237
  ## Relations
238
238
 
239
- Use `Entity.relation_components[component_key][target] = component` to associate a target entity with a component.
240
- Use `Entity.relation_tag[tag] = target` to associate a tag exclusively with a target entity.
241
- Use `Entity.relation_tags_many[tag].add(target)` to associate a tag with multiple targets.
239
+ Entity relations are unidirectional from an origin entity to possibly multiple target entities.
242
240
 
243
- Relation queries are a little more complex than other queries.
244
- Relation tags and relation components share the same space then queried, so 'normal' tags should not be in the format of a component key.
245
- Relations are unidirectional, but you can query either end of a relation.
241
+ - Use `origin.relation_tag[tag] = target` to associate an origins tag exclusively with the target entity.
242
+ This uses standard assignment and is useful for tags which would not make sense with multiple targets.
243
+ Reading `origin.relation_tag[tag]` returns a single target while enforcing the invariant of only having one target.
244
+ - Use `origin.relation_tags_many[tag].add(target)` to associate a tag with multiple targets.
245
+ This supports `set`-like syntax such as adding or removing multiple targets at once.
246
+ This allows for many-to-many relations.
247
+ - Use `origin.relation_components[component_key][target] = component` to associate a target entity with a component.
248
+ This allows storing data along with a relation.
249
+ This supports `dict`-like syntax.
250
+ The `component_key` can be queried like a normal tag.
251
+
252
+ ### Relation queries
253
+
254
+ Relations are queried with `registry.Q.all_of(relations=[...])`.
255
+ This expects 2-item or 3-item tuples following these rules:
256
+
257
+ - Use `(tag, target)` to match the origin entities with the relation `tag` to `target`.
258
+ - If `tag` is a component key then component relations are also matched.
259
+ This means you should be careful with tags which look like component keys.
260
+ - `target` can be a specific entity. This means only entities relating to that specific entity will be matched.
261
+ - `target` can be query itself. This means only entities relating to a match from the sub-query are matched.
262
+ - `target` can be `...` which means an entity with a relation to any entity is matched.
263
+ - To reverse the direction use a 3-item tuple `(origin, tag, None)`. `origin` can be anything a `target` could be.
264
+
265
+ Relations using sub-queries may be chained together.
266
+ See [Sander Mertens - Why it is time to start thinking of games as databases](https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3) to understand the repercussion of this.
267
+
268
+ You can use the following table to help with constructing relation queries:
269
+
270
+ | Matches | Syntax |
271
+ | ------------------------------------------------------------------- | :--------------------------------------: |
272
+ | Origins with a relation `tag` to `target_entity` | `(tag, target_entity)` |
273
+ | Origins with a relation `tag` to any target entity | `(tag, ...)` (Literal dot-dot-dot) |
274
+ | Origins with a relation `tag` to any targets matching a sub-query | `(tag, registry.Q.all_of(...))` |
275
+ | Targets of the relation `tag` from `origin_entity` | `(origin_entity, tag, None)` |
276
+ | Targets of the relation `tag` from any origin entity | `(..., tag, None)` (Literal dot-dot-dot) |
277
+ | Targets of the relation `tag` from any origins matching a sub-query | `(registry.Q.all_of(...), tag, None)` |
246
278
 
247
279
  ```py
248
280
  >>> @attrs.define
@@ -273,17 +305,3 @@ True
273
305
 
274
306
  ```
275
307
 
276
- ### Relation queries
277
-
278
- You can use the following table to help with constructing relation queries.
279
- `tag` is a component key if you are querying for a component relation.
280
-
281
- | Includes | Syntax |
282
- | ------------------------------------------------------------------- | :--------------------------------------: |
283
- | Entities with a relation tag to the given target | `(tag, target_entity)` |
284
- | Entities with a relation tag to any target | `(tag, ...)` (Literal dot-dot-dot) |
285
- | Entities with a relation tag to the targets in the given query | `(tag, registry.Q.all_of(...))` |
286
- | The target entities of a relation of a given entity | `(origin_entity, tag, None)` |
287
- | The target entities of any entity with the given relation tag | `(..., tag, None)` (Literal dot-dot-dot) |
288
- | The target entities of the queried entities with the given relation | `(tag, registry.Q.all_of(...))` |
289
-
@@ -207,13 +207,45 @@ True
207
207
 
208
208
  ## Relations
209
209
 
210
- Use `Entity.relation_components[component_key][target] = component` to associate a target entity with a component.
211
- Use `Entity.relation_tag[tag] = target` to associate a tag exclusively with a target entity.
212
- Use `Entity.relation_tags_many[tag].add(target)` to associate a tag with multiple targets.
210
+ Entity relations are unidirectional from an origin entity to possibly multiple target entities.
211
+
212
+ - Use `origin.relation_tag[tag] = target` to associate an origins tag exclusively with the target entity.
213
+ This uses standard assignment and is useful for tags which would not make sense with multiple targets.
214
+ Reading `origin.relation_tag[tag]` returns a single target while enforcing the invariant of only having one target.
215
+ - Use `origin.relation_tags_many[tag].add(target)` to associate a tag with multiple targets.
216
+ This supports `set`-like syntax such as adding or removing multiple targets at once.
217
+ This allows for many-to-many relations.
218
+ - Use `origin.relation_components[component_key][target] = component` to associate a target entity with a component.
219
+ This allows storing data along with a relation.
220
+ This supports `dict`-like syntax.
221
+ The `component_key` can be queried like a normal tag.
213
222
 
214
- Relation queries are a little more complex than other queries.
215
- Relation tags and relation components share the same space then queried, so 'normal' tags should not be in the format of a component key.
216
- Relations are unidirectional, but you can query either end of a relation.
223
+ ### Relation queries
224
+
225
+ Relations are queried with `registry.Q.all_of(relations=[...])`.
226
+ This expects 2-item or 3-item tuples following these rules:
227
+
228
+ - Use `(tag, target)` to match the origin entities with the relation `tag` to `target`.
229
+ - If `tag` is a component key then component relations are also matched.
230
+ This means you should be careful with tags which look like component keys.
231
+ - `target` can be a specific entity. This means only entities relating to that specific entity will be matched.
232
+ - `target` can be query itself. This means only entities relating to a match from the sub-query are matched.
233
+ - `target` can be `...` which means an entity with a relation to any entity is matched.
234
+ - To reverse the direction use a 3-item tuple `(origin, tag, None)`. `origin` can be anything a `target` could be.
235
+
236
+ Relations using sub-queries may be chained together.
237
+ See [Sander Mertens - Why it is time to start thinking of games as databases](https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3) to understand the repercussion of this.
238
+
239
+ You can use the following table to help with constructing relation queries:
240
+
241
+ | Matches | Syntax |
242
+ | ------------------------------------------------------------------- | :--------------------------------------: |
243
+ | Origins with a relation `tag` to `target_entity` | `(tag, target_entity)` |
244
+ | Origins with a relation `tag` to any target entity | `(tag, ...)` (Literal dot-dot-dot) |
245
+ | Origins with a relation `tag` to any targets matching a sub-query | `(tag, registry.Q.all_of(...))` |
246
+ | Targets of the relation `tag` from `origin_entity` | `(origin_entity, tag, None)` |
247
+ | Targets of the relation `tag` from any origin entity | `(..., tag, None)` (Literal dot-dot-dot) |
248
+ | Targets of the relation `tag` from any origins matching a sub-query | `(registry.Q.all_of(...), tag, None)` |
217
249
 
218
250
  ```py
219
251
  >>> @attrs.define
@@ -243,17 +275,3 @@ True
243
275
  True
244
276
 
245
277
  ```
246
-
247
- ### Relation queries
248
-
249
- You can use the following table to help with constructing relation queries.
250
- `tag` is a component key if you are querying for a component relation.
251
-
252
- | Includes | Syntax |
253
- | ------------------------------------------------------------------- | :--------------------------------------: |
254
- | Entities with a relation tag to the given target | `(tag, target_entity)` |
255
- | Entities with a relation tag to any target | `(tag, ...)` (Literal dot-dot-dot) |
256
- | Entities with a relation tag to the targets in the given query | `(tag, registry.Q.all_of(...))` |
257
- | The target entities of a relation of a given entity | `(origin_entity, tag, None)` |
258
- | The target entities of any entity with the given relation tag | `(..., tag, None)` (Literal dot-dot-dot) |
259
- | The target entities of the queried entities with the given relation | `(tag, registry.Q.all_of(...))` |
@@ -46,16 +46,6 @@ Changelog = "https://github.com/HexDecimal/python-tcod-ecs/blob/main/CHANGELOG.m
46
46
  Documentation = "https://python-tcod-ecs.readthedocs.io"
47
47
  Source = "https://github.com/HexDecimal/python-tcod-ecs"
48
48
 
49
- [tool.black] # https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file
50
- target-version = ["py38"]
51
- line-length = 120
52
-
53
- [tool.isort] # https://pycqa.github.io/isort/docs/configuration/options.html
54
- py_version = "38"
55
- line_length = 120
56
- profile = "black"
57
- skip_gitignore = true
58
-
59
49
  [tool.mypy] # https://mypy.readthedocs.io/en/stable/config_file.html
60
50
  files = "."
61
51
  exclude = ['^build/', '^\.']
@@ -86,44 +76,23 @@ testpaths = ["."]
86
76
  exclude_lines = ['^\s*\.\.\.', "if TYPE_CHECKING:", "# pragma: no cover"]
87
77
 
88
78
  [tool.ruff]
89
- # https://beta.ruff.rs/docs/rules/
90
- select = [
91
- "C90", # mccabe
92
- "E", # pycodestyle
93
- "W", # pycodestyle
94
- "F", # Pyflakes
95
- "I", # isort
96
- "UP", # pyupgrade
97
- "YTT", # flake8-2020
98
- "ANN", # flake8-annotations
99
- "S", # flake8-bandit
100
- "B", # flake8-bugbear
101
- "C4", # flake8-comprehensions
102
- "DTZ", # flake8-datetimez
103
- "EM", # flake8-errmsg
104
- "EXE", # flake8-executable
105
- "RET", # flake8-return
106
- "ICN", # flake8-import-conventions
107
- "PIE", # flake8-pie
108
- "PT", # flake8-pytest-style
109
- "SIM", # flake8-simplify
110
- "PTH", # flake8-use-pathlib
111
- "PL", # Pylint
112
- "TRY", # tryceratops
113
- "RUF", # NumPy-specific rules
114
- "G", # flake8-logging-format
115
- "D", # pydocstyle
116
- ]
79
+ line-length = 120
80
+
81
+ [tool.ruff.lint] # https://docs.astral.sh/ruff/rules/
82
+ select = ["ALL"]
117
83
  ignore = [
118
- "E501", # line-too-long
119
- "S101", # assert
120
84
  "ANN101", # missing-type-self
121
85
  "ANN102", # missing-type-cls
122
- "D206", # indent-with-spaces
123
- "W191", # tab-indentation
86
+ "COM", # flake8-commas, handled by formatter
87
+ "E501", # line-too-long
88
+ "S101", # assert
89
+ "SLF001", # private-member-access
90
+ "T10", # flake8-debugger
91
+ "T20", # flake8-print
124
92
  ]
125
- line-length = 120
126
93
 
127
- [tool.ruff.pydocstyle]
128
- # Use Google-style docstrings.
129
- convention = "google"
94
+ [tool.ruff.lint.isort]
95
+ required-imports = ["from __future__ import annotations"]
96
+
97
+ [tool.ruff.lint.pydocstyle]
98
+ convention = "google" # Use Google-style docstrings.
@@ -1,4 +1,5 @@
1
1
  """A type-hinted Entity Component System based on Python dictionaries and sets."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import importlib.metadata
@@ -12,9 +12,9 @@ def _is_defaultdict_type(type_hint: object) -> bool:
12
12
  return get_origin(type_hint) is defaultdict
13
13
 
14
14
 
15
- def _setup_defaultdict_factory(type_hint: type[defaultdict[Any, Any]] | type[object]) -> Callable[[], Any]:
15
+ def _setup_defaultdict_factory(type_hint: type[defaultdict[Any, Any] | object]) -> Callable[[], Any]:
16
16
  """Return the factory value for a defaultdict given its value type-hint."""
17
- assert type_hint is not Any
17
+ assert type_hint is not Any # type: ignore[comparison-overlap]
18
18
  if get_origin(type_hint) is not defaultdict:
19
19
  return get_origin(type_hint) or type_hint
20
20
  return functools.partial(defaultdict, _setup_defaultdict_factory(get_args(type_hint)[1]))
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '5.1.0'
16
- __version_tuple__ = version_tuple = (5, 1, 0)
15
+ __version__ = version = '5.2.0'
16
+ __version_tuple__ = version_tuple = (5, 2, 0)
@@ -1,14 +1,14 @@
1
1
  """ECS callback management."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
5
6
 
6
7
  from typing_extensions import TypeAlias
7
8
 
8
- from tcod.ecs.typing import ComponentKey
9
-
10
9
  if TYPE_CHECKING:
11
10
  from tcod.ecs.entity import Entity
11
+ from tcod.ecs.typing import ComponentKey
12
12
 
13
13
  _T = TypeVar("_T")
14
14
 
@@ -1,4 +1,7 @@
1
1
  """Special constants and sentinel values."""
2
+
3
+ from __future__ import annotations
4
+
2
5
  from typing import Final
3
6
 
4
7
  from sentinel_value import sentinel
@@ -1,8 +1,8 @@
1
1
  """Entity management and interface tools."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import warnings
5
- from collections.abc import Set
6
6
  from typing import (
7
7
  TYPE_CHECKING,
8
8
  Any,
@@ -30,6 +30,8 @@ from tcod.ecs.constants import IsA
30
30
  from tcod.ecs.typing import ComponentKey
31
31
 
32
32
  if TYPE_CHECKING:
33
+ from collections.abc import Set as AbstractSet
34
+
33
35
  from _typeshed import SupportsKeysAndGetItem
34
36
 
35
37
  from tcod.ecs.registry import Registry
@@ -79,7 +81,7 @@ class Entity:
79
81
  warnings.warn("Use '.registry' instead of '.world'", DeprecationWarning, stacklevel=2)
80
82
  return self.registry
81
83
 
82
- def __new__(cls, registry: Registry, uid: object = object) -> Entity:
84
+ def __new__(cls, registry: Registry, uid: object = object) -> Entity: # noqa: PYI034
83
85
  """Return a unique entity for the given `registry` and `uid`.
84
86
 
85
87
  If an entity already exists with a matching `registry` and `uid` then that entity is returned.
@@ -333,7 +335,7 @@ class Entity:
333
335
  >>> registry["foo"]
334
336
  <Entity(uid='foo')>
335
337
  """
336
- uid_str = f"object at 0x{id(self.uid):X}" if self.uid.__class__ == object else repr(self.uid)
338
+ uid_str = f"object at 0x{id(self.uid):X}" if self.uid.__class__ is object else repr(self.uid)
337
339
  items = [f"{self.__class__.__name__}(uid={uid_str})"]
338
340
  name = self.name
339
341
  if name is not None: # Switch to older style
@@ -425,7 +427,7 @@ class EntityComponents(MutableMapping[Union[Type[Any], Tuple[object, Type[Any]]]
425
427
  for entity in _traverse_entities(self.entity, self.traverse):
426
428
  try:
427
429
  return _components_by_entity[entity][key] # type: ignore[no-any-return]
428
- except KeyError:
430
+ except KeyError: # noqa: PERF203
429
431
  pass
430
432
  raise KeyError(key)
431
433
 
@@ -460,7 +462,7 @@ class EntityComponents(MutableMapping[Union[Type[Any], Tuple[object, Type[Any]]]
460
462
  tcod.ecs.query._touch_component(self.entity.registry, key) # Component removed
461
463
  tcod.ecs.callbacks._on_component_changed(key, self.entity, old_value, None)
462
464
 
463
- def keys(self) -> Set[ComponentKey[object]]: # type: ignore[override]
465
+ def keys(self) -> AbstractSet[ComponentKey[object]]: # type: ignore[override]
464
466
  """Return the components held by this entity, including inherited components."""
465
467
  _components_by_entity = self.entity.registry._components_by_entity
466
468
  if not self.traverse:
@@ -512,12 +514,10 @@ class EntityComponents(MutableMapping[Union[Type[Any], Tuple[object, Type[Any]]]
512
514
  yield key_name, key_component
513
515
 
514
516
  @overload
515
- def __ior__(self, value: SupportsKeysAndGetItem[ComponentKey[Any], Any]) -> Self:
516
- ...
517
+ def __ior__(self, value: SupportsKeysAndGetItem[ComponentKey[Any], Any]) -> Self: ...
517
518
 
518
519
  @overload
519
- def __ior__(self, value: Iterable[tuple[ComponentKey[Any], Any]]) -> Self:
520
- ...
520
+ def __ior__(self, value: Iterable[tuple[ComponentKey[Any], Any]]) -> Self: ...
521
521
 
522
522
  def __ior__(
523
523
  self, value: SupportsKeysAndGetItem[ComponentKey[Any], Any] | Iterable[tuple[ComponentKey[Any], Any]]
@@ -612,7 +612,7 @@ class EntityTags(MutableSet[Any]):
612
612
  """Return the number of tags this entity has."""
613
613
  return len(self._as_set())
614
614
 
615
- def __ior__(self, other: Set[object]) -> Self:
615
+ def __ior__(self, other: AbstractSet[object]) -> Self:
616
616
  """Add tags in-place.
617
617
 
618
618
  .. versionadded:: 3.3
@@ -621,7 +621,7 @@ class EntityTags(MutableSet[Any]):
621
621
  self.add(to_add)
622
622
  return self
623
623
 
624
- def __isub__(self, other: Set[Any]) -> Self:
624
+ def __isub__(self, other: AbstractSet[Any]) -> Self:
625
625
  """Remove tags in-place.
626
626
 
627
627
  .. versionadded:: 3.3
@@ -788,10 +788,10 @@ class EntityRelations(MutableMapping[object, EntityRelationsMapping]):
788
788
  def __iter__(self) -> Iterator[Any]:
789
789
  """Iterate over the unique relation tags of this entity."""
790
790
  _relation_tags_by_entity = self.entity.registry._relation_tags_by_entity
791
- EMPTY_DICT: dict[object, set[Entity]] = {}
791
+ empty_dict: dict[object, set[Entity]] = {}
792
792
  yield from set().union(
793
793
  *(
794
- _relation_tags_by_entity.get(entity, EMPTY_DICT).keys()
794
+ _relation_tags_by_entity.get(entity, empty_dict).keys()
795
795
  for entity in _traverse_entities(self.entity, self.traverse)
796
796
  )
797
797
  )
@@ -926,7 +926,7 @@ class EntityComponentRelationMapping(Generic[T], MutableMapping[Entity, T]):
926
926
 
927
927
  _relations_lookup_discard(registry, self.entity, self.key, target)
928
928
 
929
- def keys(self) -> Set[Entity]: # type: ignore[override]
929
+ def keys(self) -> AbstractSet[Entity]: # type: ignore[override]
930
930
  """Return all entities with an associated component value."""
931
931
  _relation_components_by_entity = self.entity.registry._relation_components_by_entity
932
932
  result: set[Entity] = set()
@@ -1004,7 +1004,7 @@ class EntityComponentRelations(MutableMapping[ComponentKey[Any], EntityComponent
1004
1004
  for component_key in list(self.entity.registry._relation_components_by_entity.get(self.entity, ())):
1005
1005
  self[component_key].clear()
1006
1006
 
1007
- def keys(self) -> Set[ComponentKey[object]]: # type: ignore[override]
1007
+ def keys(self) -> AbstractSet[ComponentKey[object]]: # type: ignore[override]
1008
1008
  """Returns the components keys this entity has relations for."""
1009
1009
  _relation_components_by_entity = self.entity.registry._relation_components_by_entity
1010
1010
  return set().union(
@@ -1,10 +1,10 @@
1
1
  """Tools for querying Registry objects."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import itertools
5
6
  import warnings
6
7
  from collections import defaultdict
7
- from collections.abc import Set
8
8
  from typing import TYPE_CHECKING, Any, Iterable, Iterator, Protocol, TypeVar, overload
9
9
  from weakref import WeakKeyDictionary, WeakSet
10
10
 
@@ -13,11 +13,13 @@ from typing_extensions import Self
13
13
 
14
14
  import tcod.ecs.entity
15
15
  from tcod.ecs.constants import IsA
16
- from tcod.ecs.typing import ComponentKey, _RelationQuery
17
16
 
18
17
  if TYPE_CHECKING:
18
+ from collections.abc import Set as AbstractSet
19
+
19
20
  from tcod.ecs.entity import Entity
20
21
  from tcod.ecs.registry import Registry
22
+ from tcod.ecs.typing import ComponentKey, _RelationQuery
21
23
 
22
24
  _T1 = TypeVar("_T1")
23
25
  _T2 = TypeVar("_T2")
@@ -34,7 +36,7 @@ _query_caches: WeakKeyDictionary[Registry, _QueryCache] = WeakKeyDictionary()
34
36
  class _QueryCache:
35
37
  """Main data structure for the query cache."""
36
38
 
37
- queries: dict[_Query, Set[Entity]] = attrs.field(factory=dict)
39
+ queries: dict[_Query, AbstractSet[Entity]] = attrs.field(factory=dict)
38
40
  """Table of cached queries."""
39
41
  by_components: defaultdict[ComponentKey[object], WeakSet[_Query]] = attrs.field(
40
42
  factory=lambda: defaultdict(WeakSet)
@@ -100,7 +102,7 @@ def _check_suspicious_tags(tags: Iterable[object], stacklevel: int = 2) -> None:
100
102
  )
101
103
 
102
104
 
103
- def _fetch_relation_table(registry: Registry, relation: _RelationQuery) -> Set[Entity]:
105
+ def _fetch_relation_table(registry: Registry, relation: _RelationQuery) -> AbstractSet[Entity]:
104
106
  """Get the entity table for this relation.
105
107
 
106
108
  For simple cases where target/origin is `Entity | ...` this returns the set directly from the lookup table.
@@ -130,7 +132,7 @@ def _get_query_cache(registry: Registry) -> _QueryCache:
130
132
  return cache
131
133
 
132
134
 
133
- def _get_query(registry: Registry, query: _Query) -> Set[Entity]:
135
+ def _get_query(registry: Registry, query: _Query) -> AbstractSet[Entity]:
134
136
  """Return the entities for the given query and registry."""
135
137
  cache = _get_query_cache(registry)
136
138
  if cache is not None:
@@ -167,7 +169,7 @@ class _Query(Protocol):
167
169
  """Add this query to the local cache."""
168
170
  ...
169
171
 
170
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
172
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]:
171
173
  """Compile the entities of this query, returning a set which must not be modified."""
172
174
  ...
173
175
 
@@ -178,10 +180,10 @@ class _QueryComponent:
178
180
 
179
181
  _component: ComponentKey[object]
180
182
 
181
- def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None:
183
+ def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None: # noqa: ARG002
182
184
  cache.by_components[self._component].add(self)
183
185
 
184
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
186
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
185
187
  return registry._components_by_type.get(self._component, {}).keys()
186
188
 
187
189
 
@@ -191,10 +193,10 @@ class _QueryTag:
191
193
 
192
194
  _tag: object
193
195
 
194
- def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None:
196
+ def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None: # noqa: ARG002
195
197
  cache.by_tags[self._tag].add(self)
196
198
 
197
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
199
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
198
200
  return registry._tags_by_key.get(self._tag, set())
199
201
 
200
202
 
@@ -220,7 +222,7 @@ class _QueryRelation:
220
222
  if w_query is not None:
221
223
  _get_query_cache(w_query.registry).dependencies[w_query._query].add((registry, self))
222
224
 
223
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
225
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
224
226
  return _fetch_relation_table(registry, self._relation)
225
227
 
226
228
 
@@ -242,7 +244,7 @@ class _QueryLogicalAnd:
242
244
  for dependency in itertools.chain(self._all_of, self._none_of):
243
245
  cache.dependencies[dependency].add((registry, self))
244
246
 
245
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
247
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
246
248
  if len(self._all_of) == 1 and not self._none_of: # Only one sub-query, simply return the results of it
247
249
  return _get_query(registry, next(iter(self._all_of))) # Avoids an extra copy of a set
248
250
  requires = sorted( # Place the smallest sets first to speed up intersections
@@ -271,7 +273,7 @@ class _QueryLogicalOr:
271
273
  for dependency in self._any_of:
272
274
  cache.dependencies[dependency].add((registry, self))
273
275
 
274
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
276
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
275
277
  if len(self._any_of) == 1: # If there is only one sub-query then simply return the results of it
276
278
  return _get_query(registry, next(iter(self._any_of))) # Avoids an extra copy of a set
277
279
  entities: set[Entity] = set()
@@ -300,7 +302,7 @@ class _QueryTraversalPropagation:
300
302
  cache.dependencies[self._sub_query].add((registry, self))
301
303
  cache.dependencies[self._get_traverse_query()].add((registry, self))
302
304
 
303
- def _compile(self, registry: Registry, cache: _QueryCache) -> Set[Entity]:
305
+ def _compile(self, registry: Registry, cache: _QueryCache) -> AbstractSet[Entity]: # noqa: ARG002
304
306
  cumulative_set = set(_get_query(registry, self._sub_query)) # All entities touched by this traversal
305
307
  relations_set = _get_query(
306
308
  registry, self._get_traverse_query()
@@ -341,7 +343,7 @@ class BoundQuery:
341
343
  warnings.warn("Use '.registry' instead of '.world'", DeprecationWarning, stacklevel=2)
342
344
  return self.registry
343
345
 
344
- def get_entities(self) -> Set[Entity]:
346
+ def get_entities(self) -> AbstractSet[Entity]:
345
347
  """Return entities matching the current query as a read-only set.
346
348
 
347
349
  This is useful for post-processing the results of a query using set operations.
@@ -350,6 +352,10 @@ class BoundQuery:
350
352
  """
351
353
  return _get_query(self.registry, self._query)
352
354
 
355
+ def __bool__(self) -> bool:
356
+ """Return True if any entity matches this query."""
357
+ return bool(self.get_entities())
358
+
353
359
  @staticmethod
354
360
  def __as_queries(
355
361
  components: Iterable[ComponentKey[object]] = (),
@@ -364,7 +370,7 @@ class BoundQuery:
364
370
  yield from (_QueryTraversalPropagation(_QueryTag(tag), traverse, depth) for tag in tags)
365
371
  yield from (_QueryTraversalPropagation(_QueryRelation(relations), traverse, depth) for relations in relations)
366
372
 
367
- def all_of( # noqa: PLR0913
373
+ def all_of(
368
374
  self,
369
375
  components: Iterable[ComponentKey[object]] = (),
370
376
  *,
@@ -381,7 +387,7 @@ class BoundQuery:
381
387
  & self._query,
382
388
  )
383
389
 
384
- def none_of( # noqa: PLR0913
390
+ def none_of(
385
391
  self,
386
392
  components: Iterable[ComponentKey[object]] = (),
387
393
  *,
@@ -403,42 +409,36 @@ class BoundQuery:
403
409
  return iter(self.get_entities())
404
410
 
405
411
  @overload
406
- def __getitem__(self, key: tuple[ComponentKey[_T1]]) -> Iterable[tuple[_T1]]:
407
- ...
412
+ def __getitem__(self, key: tuple[ComponentKey[_T1]]) -> Iterable[tuple[_T1]]: ...
408
413
 
409
414
  @overload
410
- def __getitem__(self, key: tuple[ComponentKey[_T1], ComponentKey[_T2]]) -> Iterable[tuple[_T1, _T2]]:
411
- ...
415
+ def __getitem__(self, key: tuple[ComponentKey[_T1], ComponentKey[_T2]]) -> Iterable[tuple[_T1, _T2]]: ...
412
416
 
413
417
  @overload
414
418
  def __getitem__(
415
419
  self, key: tuple[ComponentKey[_T1], ComponentKey[_T2], ComponentKey[_T3]]
416
- ) -> Iterable[tuple[_T1, _T2, _T3]]:
417
- ...
420
+ ) -> Iterable[tuple[_T1, _T2, _T3]]: ...
418
421
 
419
422
  @overload
420
423
  def __getitem__(
421
424
  self, key: tuple[ComponentKey[_T1], ComponentKey[_T2], ComponentKey[_T3], ComponentKey[_T4]]
422
- ) -> Iterable[tuple[_T1, _T2, _T3, _T4]]:
423
- ...
425
+ ) -> Iterable[tuple[_T1, _T2, _T3, _T4]]: ...
424
426
 
425
427
  @overload
426
428
  def __getitem__(
427
429
  self,
428
430
  key: tuple[ComponentKey[_T1], ComponentKey[_T2], ComponentKey[_T3], ComponentKey[_T4], ComponentKey[_T5]],
429
- ) -> Iterable[tuple[_T1, _T2, _T3, _T4, _T5]]:
430
- ...
431
+ ) -> Iterable[tuple[_T1, _T2, _T3, _T4, _T5]]: ...
431
432
 
432
433
  @overload
433
- def __getitem__(self, key: tuple[ComponentKey[object], ...]) -> Iterable[tuple[Any, ...]]:
434
- ...
434
+ def __getitem__(self, key: tuple[ComponentKey[object], ...]) -> Iterable[tuple[Any, ...]]: ...
435
435
 
436
436
  def __getitem__(self, key: tuple[ComponentKey[object], ...]) -> Iterable[tuple[Any, ...]]:
437
437
  """Collect components from a query."""
438
438
  assert key is not None
439
439
  assert isinstance(key, tuple)
440
440
 
441
- Entity = tcod.ecs.entity.Entity
441
+ Entity = tcod.ecs.entity.Entity # noqa: N806
442
442
 
443
443
  entities = list(self.all_of(components=set(key) - {Entity}).get_entities())
444
444
  entity_components = []
@@ -1,16 +1,19 @@
1
1
  """Registry management tools."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import warnings
5
6
  from collections import defaultdict
6
- from typing import Any, DefaultDict, Dict, Iterable, Mapping, NoReturn, Set, TypeVar
7
+ from typing import TYPE_CHECKING, Any, DefaultDict, Dict, Final, Iterable, Mapping, NoReturn, Set, TypeVar
7
8
 
8
9
  import attrs
9
10
 
10
11
  import tcod.ecs._converter
11
12
  import tcod.ecs.query
12
13
  from tcod.ecs.entity import Entity
13
- from tcod.ecs.typing import ComponentKey, _RelationTargetLookup
14
+
15
+ if TYPE_CHECKING:
16
+ from tcod.ecs.typing import ComponentKey, _RelationTargetLookup
14
17
 
15
18
  _T1 = TypeVar("_T1")
16
19
  _T2 = TypeVar("_T2")
@@ -110,9 +113,9 @@ class Registry:
110
113
 
111
114
  dict[entity][tag] = {target_entities}
112
115
  """
113
- _relation_components_by_entity: defaultdict[
114
- Entity, defaultdict[ComponentKey[object], dict[Entity, Any]]
115
- ] = attrs.field(init=False, factory=lambda: defaultdict(_defaultdict_of_dict))
116
+ _relation_components_by_entity: defaultdict[Entity, defaultdict[ComponentKey[object], dict[Entity, Any]]] = (
117
+ attrs.field(init=False, factory=lambda: defaultdict(_defaultdict_of_dict))
118
+ )
116
119
  """Random access relations owning components.
117
120
 
118
121
  dict[entity][ComponentKey][target_entity] = component
@@ -177,7 +180,7 @@ class Registry:
177
180
  global_: Entity | None = state.pop("global_", None) # Migrate from version <=1.2.0
178
181
 
179
182
  # These attributes contain redundant data and will be removed
180
- REDUNDANT_ATTRIBUTES = frozenset(
183
+ redundant_attributes: Final = frozenset(
181
184
  {
182
185
  "_components_by_entity", # <=3.4.0
183
186
  "_tags_by_key", # <=3.4.0
@@ -185,7 +188,7 @@ class Registry:
185
188
  "_names_by_entity", # <=3.4.0
186
189
  }
187
190
  )
188
- for ignored in REDUNDANT_ATTRIBUTES:
191
+ for ignored in redundant_attributes:
189
192
  state.pop(ignored, None)
190
193
 
191
194
  converter = tcod.ecs._converter._get_converter()
@@ -306,7 +309,7 @@ class Registry:
306
309
  return entity
307
310
 
308
311
  @property
309
- def Q(self) -> tcod.ecs.query.BoundQuery:
312
+ def Q(self) -> tcod.ecs.query.BoundQuery: # noqa: N802
310
313
  """Start a new Query for this registry.
311
314
 
312
315
  Alias for ``tcod.ecs.Query(registry)``.
@@ -1,4 +1,5 @@
1
1
  """Common type-hints for tcod.ecs."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import sys
@@ -30,7 +31,7 @@ _RelationTargetLookup: TypeAlias = Union[Entity, EllipsisType]
30
31
  _RelationQueryTarget: TypeAlias = Union[_RelationTargetLookup, BoundQuery]
31
32
  """Possible target for relation queries."""
32
33
 
33
- _RelationQuery: TypeAlias = Union[Tuple[object, _RelationQueryTarget], Tuple[_RelationQueryTarget, object, None]]
34
+ _RelationQuery: TypeAlias = Union[Tuple[object, _RelationQueryTarget], Tuple[_RelationQueryTarget, object, None]] # noqa: PYI047
34
35
  """Query format for relations.
35
36
 
36
37
  One of 4 formats:
@@ -1,4 +1,6 @@
1
1
  # noqa: D100
2
+ from __future__ import annotations
3
+
2
4
  __all__ = ("World",)
3
5
 
4
6
  from tcod.ecs.registry import Registry as World
File without changes
File without changes