Skip to content

Commit cd54e54

Browse files
refactor: even simpler boilerplate, all types now just called 'ID'
1 parent 959f9e6 commit cd54e54

10 files changed

Lines changed: 47 additions & 59 deletions

File tree

src/openedx_content/applets/components/models.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ def __str__(self) -> str:
8585
return f"{self.namespace}:{self.name}"
8686

8787

88-
ComponentID = NewType("ComponentID", PublishableEntity.ID)
89-
90-
9188
class Component(PublishableEntityMixin):
9289
"""
9390
This represents any Component that has ever existed in a LearningPackage.
@@ -133,11 +130,11 @@ class Component(PublishableEntityMixin):
133130
that will exist for as long as the LearningPackage itself exists.
134131
"""
135132

136-
type ID = ComponentID
133+
ID = NewType("ID", PublishableEntity.ID)
137134

138135
@property
139-
def id(self) -> ComponentID:
140-
return cast(ComponentID, self.publishable_entity_id)
136+
def id(self) -> ID:
137+
return cast(Component.ID, self.publishable_entity_id)
141138

142139
@property
143140
@deprecated("Use .id instead")

src/openedx_content/applets/containers/models.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,6 @@ def __str__(self) -> str: # pylint: disable=invalid-str-returned
150150
return self.type_code
151151

152152

153-
ContainerID = NewType("ContainerID", PublishableEntity.ID)
154-
155-
156153
class Container(PublishableEntityMixin):
157154
"""
158155
A Container is a type of PublishableEntity that holds other
@@ -165,7 +162,7 @@ class Container(PublishableEntityMixin):
165162
entities for different learners or at different times.
166163
"""
167164

168-
type ID = ContainerID
165+
ID = NewType("ID", PublishableEntity.ID)
169166

170167
type_code: str # Subclasses must override this, e.g. "unit"
171168
# olx_code: the OLX <tag_name> for XML serialization. Subclasses _may_ override this.
@@ -182,8 +179,8 @@ class Container(PublishableEntityMixin):
182179
)
183180

184181
@property
185-
def id(self) -> ContainerID:
186-
return cast(ContainerID, self.publishable_entity_id)
182+
def id(self) -> ID:
183+
return cast(Container.ID, self.publishable_entity_id)
187184

188185
@property
189186
@deprecated("Use .id instead")

src/openedx_content/applets/publishing/models/learning_package.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from openedx_django_lib.fields import (
1111
MultiCollationTextField,
12-
TypedBigAutoField,
12+
TypedAutoField,
1313
case_insensitive_char_field,
1414
immutable_uuid_field,
1515
key_field,
@@ -29,17 +29,13 @@ class LearningPackage(models.Model):
2929
# site. Furthermore, many, many things have foreign keys to this model and
3030
# uniqueness indexes on those foreign keys + their own fields, so the 4
3131
# bytes saved will add up over time.
32-
LearningPackageID = NewType("LearningPackageID", int)
33-
type ID = LearningPackageID
34-
35-
class IDField(TypedBigAutoField[ID]):
36-
pass
37-
32+
ID = NewType("ID", int)
33+
class IDField(TypedAutoField[ID]): ... # Note: this is ...AutoField not ...BigAutoField
3834
id = IDField(primary_key=True)
3935

4036
@property # type: ignore[no-redef]
4137
@deprecated("Use .id instead")
42-
def pk(self) -> LearningPackageID:
38+
def pk(self) -> ID:
4339
return self.id
4440

4541
uuid = immutable_uuid_field()

src/openedx_content/applets/publishing/models/publishable_entity.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,9 @@ class PublishableEntity(models.Model):
112112
mixins will provide a little syntactic sugar to make common access patterns
113113
more convenient, like file access.
114114
"""
115-
PublishableEntityID = NewType("PublishableEntityID", int)
116-
type ID = PublishableEntityID
117-
118-
class IDField(TypedBigAutoField[ID]):
119-
pass
120-
115+
# Boilerplate for fully-typed ID field:
116+
ID = NewType("ID", int)
117+
class IDField(TypedBigAutoField[ID]): ...
121118
id = IDField(primary_key=True)
122119

123120
uuid = immutable_uuid_field()
@@ -146,7 +143,7 @@ class IDField(TypedBigAutoField[ID]):
146143

147144
@property # type: ignore[no-redef]
148145
@deprecated("Use .id instead")
149-
def pk(self) -> PublishableEntityID:
146+
def pk(self) -> ID:
150147
return self.id
151148

152149
class Meta:

src/openedx_content/applets/sections/models.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db import models
99

1010
from ..containers.api import get_container_subclass_of
11-
from ..containers.models import Container, ContainerID, ContainerVersion
11+
from ..containers.models import Container, ContainerVersion
1212
from ..publishing.models import PublishableEntity
1313
from ..subsections.models import Subsection
1414

@@ -17,8 +17,6 @@
1717
"SectionVersion",
1818
]
1919

20-
SectionID = NewType("SectionID", ContainerID)
21-
2220

2321
@Container.register_subclass
2422
class Section(Container):
@@ -29,7 +27,7 @@ class Section(Container):
2927
entities and can be added to other containers.
3028
"""
3129

32-
type ID = SectionID
30+
ID = NewType("ID", Container.ID)
3331

3432
type_code = "section"
3533
olx_tag_name = "chapter" # Serializes to OLX as `<chapter>...</chapter>`.
@@ -42,8 +40,8 @@ class Section(Container):
4240
)
4341

4442
@property
45-
def id(self) -> SectionID:
46-
return cast(SectionID, self.publishable_entity_id)
43+
def id(self) -> ID:
44+
return cast(Section.ID, self.publishable_entity_id)
4745

4846
@override
4947
@classmethod

src/openedx_content/applets/subsections/models.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db import models
99

1010
from ..containers.api import get_container_subclass_of
11-
from ..containers.models import Container, ContainerID, ContainerVersion
11+
from ..containers.models import Container, ContainerVersion
1212
from ..publishing.models import PublishableEntity
1313
from ..units.models import Unit
1414

@@ -17,8 +17,6 @@
1717
"SubsectionVersion",
1818
]
1919

20-
SubsectionID = NewType("SubsectionID", ContainerID)
21-
2220

2321
@Container.register_subclass
2422
class Subsection(Container):
@@ -29,7 +27,7 @@ class Subsection(Container):
2927
entities and can be added to other containers.
3028
"""
3129

32-
type ID = SubsectionID
30+
ID = NewType("ID", Container.ID)
3331

3432
type_code = "subsection"
3533
olx_tag_name = "sequential" # Serializes to OLX as `<sequential>...</sequential>`.
@@ -42,8 +40,8 @@ class Subsection(Container):
4240
)
4341

4442
@property
45-
def id(self) -> SubsectionID:
46-
return cast(SubsectionID, self.publishable_entity_id)
43+
def id(self) -> ID:
44+
return cast(Subsection.ID, self.publishable_entity_id)
4745

4846
@override
4947
@classmethod

src/openedx_content/applets/units/models.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@
77
from django.core.exceptions import ValidationError
88
from django.db import models
99

10-
from ..containers.models import Container, ContainerID, ContainerVersion
10+
from ..containers.models import Container, ContainerVersion
1111
from ..publishing.models import PublishableEntity
1212

1313
__all__ = [
1414
"Unit",
1515
"UnitVersion",
1616
]
1717

18-
UnitID = NewType("UnitID", ContainerID)
19-
2018

2119
@Container.register_subclass
2220
class Unit(Container):
@@ -27,7 +25,7 @@ class Unit(Container):
2725
entities and can be added to other containers.
2826
"""
2927

30-
type ID = UnitID
28+
ID = NewType("ID", Container.ID)
3129

3230
type_code = "unit"
3331
olx_tag_name = "vertical" # Serializes to OLX as `<unit>...</unit>`.
@@ -40,8 +38,8 @@ class Unit(Container):
4038
)
4139

4240
@property
43-
def id(self) -> UnitID:
44-
return cast(UnitID, self.publishable_entity_id)
41+
def id(self) -> ID:
42+
return cast(Unit.ID, self.publishable_entity_id)
4543

4644
@override
4745
@classmethod

src/openedx_django_lib/fields.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
from django.db import models
1818

1919
from .collations import MultiCollationMixin
20-
# Re-export this field which is in a separate file so we can use a .pyi file with it:
21-
from .id_fields import TypedBigAutoField # pylint: disable=unused-import
20+
# Re-export these fields which are in a separate file so we can use .pyi type stubs:
21+
from .id_fields import TypedAutoField, TypedBigAutoField # pylint: disable=unused-import
2222
from .validators import validate_utc_datetime
2323

2424

src/openedx_django_lib/id_fields.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,13 @@ class TypedBigAutoField(models.BigAutoField):
1212
1313
```
1414
class MyModel(models.Model):
15-
MyModelID = NewType("MyModelID", int)
16-
type ID = MyModelID
17-
18-
class IDField(TypedBigAutoField[ID]):
19-
pass
20-
15+
# Boilerplate for fully-typed ID field:
16+
ID = NewType("ID", int)
17+
class IDField(TypedBigAutoField[ID]): ...
2118
id = IDField(primary_key=True)
2219
23-
...
20+
... # rest of your model's fields...
2421
```
25-
26-
Note: using a full explicit name like "MyModelID" for the `NewType(...)` and
27-
then aliasing it to `ID` is recommended, so you can use `MyModel.ID`
28-
everywhere for convenience, but will still see "MyModelID" in the mypy
29-
output, as opposed to confusing messages like "expected 'ID' but got 'ID'".
3022
"""
3123

3224
# The actual typing + django-stubs "magic" is handled entirely by the .pyi file.
@@ -37,3 +29,14 @@ def __class_getitem__(cls, _):
3729
# and `class XxxField(TypedBigAutoField[XxxID])` work without `Generic` in the MRO.
3830
# (Including `Generic` as a superclass here breaks Django's field __deepcopy__.)
3931
return cls
32+
33+
34+
class TypedAutoField(models.AutoField):
35+
"""
36+
Use sparingly. This is a 32-bit version of `TypedBigAutoField`, and you
37+
should usually be using that instead of this for primary key fields.
38+
"""
39+
40+
@classmethod
41+
def __class_getitem__(cls, _): # See explanation on the class above.
42+
return cls

src/openedx_django_lib/id_fields.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ _IDType = TypeVar("_IDType", bound=int)
1111
class TypedBigAutoField(models.BigAutoField[_IDType, _IDType]):
1212
_pyi_private_set_type: _IDType | int
1313
_pyi_private_get_type: _IDType
14+
15+
class TypedAutoField(models.AutoField[_IDType, _IDType]):
16+
_pyi_private_set_type: _IDType | int
17+
_pyi_private_get_type: _IDType

0 commit comments

Comments
 (0)