Skip to content

Commit d658926

Browse files
committed
fix: add uuids
1 parent 013635d commit d658926

1 file changed

Lines changed: 67 additions & 9 deletions

File tree

oeps/best-practices/oep-0068-bp-naming-identifiers.rst

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ OEP-68: Naming Identifiers
3636
Abstract
3737
********
3838

39-
Open edX code uses several distinct types of identifiers. To make code unambiguous and readable, we adopt consistent naming conventions for each type. This OEP defines four categories of identifiers—Primary Keys, OpaqueKeys, OpaqueKey Strings, and Codes—and specifies the naming conventions for each. These conventions apply across Python code, database column names, REST API fields, event schemas, and any other context where identifiers appear.
39+
Open edX code uses several distinct types of identifiers. To make code unambiguous and readable, we adopt consistent naming conventions for each type. This OEP defines five categories of identifiers—Primary Keys, OpaqueKeys, Codes, UUIDs, and Other—and specifies the naming conventions for each. These conventions apply across Python code, database column names, REST API fields, event schemas, and any other context where identifiers appear.
4040

4141
Motivation
4242
**********
@@ -50,7 +50,7 @@ Consistent naming makes identifier types immediately apparent from the name alon
5050
Specification
5151
*************
5252

53-
There are four recognized categories of identifier in Open edX code. When naming an identifier, first determine which category it belongs to, then apply the naming convention for that category. If an identifier does not fit any category, choose a name that does not collide with any of the four conventions, so that readers are not misled.
53+
There are five recognized categories of identifier in Open edX code. When naming an identifier, first determine which category it belongs to, then apply the naming convention for that category. If an identifier does not fit any category, choose a name that does not collide with any of the five conventions, so that readers are not misled.
5454

5555
These conventions apply wherever identifiers are named: Python variables, parameters, and attributes; Django model field names and database column names; REST API request and response field names; and event data schema fields.
5656

@@ -70,7 +70,7 @@ Summary
7070
- ``int``
7171
- ``pk``, ``*_pk``
7272
* - OpaqueKey
73-
- Globally-scoped parsed key object
73+
- Parsed key object scoped to one Open edX instance
7474
- subclass of ``OpaqueKey``
7575
- ``*_key``
7676
* - OpaqueKey String
@@ -81,6 +81,14 @@ Summary
8181
- Locally-scoped slug-like string
8282
- ``str``
8383
- ``*_code``
84+
* - UUID
85+
- Globally unique identifier scoped across all Open edX instances
86+
- ``uuid.UUID``
87+
- ``*_uuid``
88+
* - UUID String
89+
- Serialized form of a UUID
90+
- ``str``
91+
- ``*_uuid_string`` (or ``*_uuid`` when unambiguous)
8492

8593
Primary Keys
8694
============
@@ -124,7 +132,7 @@ Please note that, for historical reasons, concrete OpaqueKey subclasses use the
124132
.. _openedx/opaque-keys: https://github.com/openedx/opaque-keys
125133

126134
OpaqueKey Strings
127-
=================
135+
-----------------
128136

129137
The serialized (string) form of an OpaqueKey is distinct from the parsed object. When the context makes it unambiguous that the value is a string—such as inside a Django form, serializer, or REST API field—it is acceptable to use ``*_key`` for the serialized form as well.
130138

@@ -140,13 +148,17 @@ When there is any ambiguity about whether a value is a parsed ``OpaqueKey`` obje
140148
def _get_context_key_if_valid(serializer) -> LearningContextKey | None:
141149
usage_key_string = serializer.cleaned_data.get('usage_key')
142150
if not usage_key_string:
143-
return None:
151+
return None
144152
try:
145153
return UsageKey.from_string(usage_key_string).context_key
146154
except InvalidKeyError:
147155
return None
148156
149-
Please note that OpaqueKeys Strings should only be used at the boundaries of the platform (REST APIs, external events, logging, etc.). Within the system, parsed OpaqueKey objects are always preferred, as they protect against serializationd-deserializatione errors and provide type safety.
157+
Please note that OpaqueKey Strings should only be used at the boundaries of the platform (REST APIs, external events, logging, etc.). Within the system, parsed OpaqueKey objects are always preferred, as they protect against serialization-deserialization errors and provide type safety.
158+
159+
.. note::
160+
161+
On the frontend, parsed ``OpaqueKey`` objects are not available; OpaqueKeys are always plain strings. Therefore, a ``*KeyString`` suffix is not needed in frontend code—``*Key`` is always acceptable.
150162

151163
Codes
152164
=====
@@ -168,19 +180,65 @@ Variables and fields holding a code should use the suffix ``_code``.
168180
169181
def get_library(org_code: str, library_code: str) -> ContentLibrary: ...
170182
183+
UUIDs
184+
=====
185+
186+
A UUID (Universally Unique Identifier) is a 128-bit identifier that uniquely identifies a resource across *all* Open edX instances, not just within one. UUIDs are suitable for use as stable identifiers in cross-instance contexts such as event data, external integrations, and shared databases.
187+
188+
The preferred Python type for a UUID is ``uuid.UUID``. Variables and fields holding a UUID should use the suffix ``_uuid``.
189+
190+
.. code-block:: python
191+
192+
import uuid
193+
194+
discussion_uuid: uuid.UUID = thread.uuid
195+
enrollment_uuid: uuid.UUID = enrollment.uuid
196+
197+
def get_discussion_thread(discussion_uuid: uuid.UUID) -> DiscussionThread: ...
198+
199+
class DiscussionThread(models.Model):
200+
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
201+
202+
UUID Strings
203+
------------
204+
205+
The serialized (string) form of a UUID is distinct from the ``uuid.UUID`` object. When the context makes it unambiguous that the value is a string—such as inside a Django serializer or REST API field—it is acceptable to use ``*_uuid`` for the serialized form as well.
206+
207+
When there is any ambiguity about whether a value is a ``uuid.UUID`` object or its string serialization, use the suffix ``*_uuid_string`` to make the distinction explicit.
208+
209+
.. code-block:: python
210+
211+
# Unambiguous context (serializer field): *_uuid is fine
212+
class ThreadSerializer(serializers.Serializer):
213+
discussion_uuid = serializers.UUIDField()
214+
215+
# Ambiguous context: use *_uuid_string to distinguish from the UUID object
216+
def _get_thread_by_discussion(discussion_uuid_string: str) -> DiscussionThread:
217+
try:
218+
discussion_uuid = uuid.UUID(discussion_uuid_string)
219+
except ValueError:
220+
raise BadDiscussionUUID(discussion_uuid)
221+
return DiscussionThread.objects.get(uuid=discussion_uuid)
222+
223+
As with OpaqueKey Strings, UUID Strings should only be used at the boundaries of the platform. Within the system, ``uuid.UUID`` objects are always preferred.
224+
225+
.. note::
226+
227+
On the frontend, ``uuid.UUID`` objects are not available; UUIDs are always plain strings. Therefore, a ``*UuidString`` suffix is not needed in frontend code—``*Uuid`` is always acceptable.
228+
171229
Other Identifiers
172230
=================
173231

174-
Not every identifier fits neatly into one of the above categories. When an identifier does not, choose a name that does not use any of the suffixes ``_pk``, ``_key``, ``_key_string``, or ``_code``, so that readers are not misled into assuming a type or scope that does not apply.
232+
Not every identifier fits neatly into one of the above categories. When an identifier does not, choose a name that does not use any of the suffixes ``_pk``, ``_key``, ``_key_string``, ``_code``, ``_uuid``, or ``_uuid_string``, so that readers are not misled into assuming a type or scope that does not apply.
175233

176234
For inspiration, consider the ``refname`` field on ``PublishableEntity`` objects in ``openedx-learning``. A ``refname`` correlates a database entity with its representation in off-platform or cross-platform ZIP archives. It is not a primary key (which would be database-specific), not a code (because it may contain non-slug characters), and not an OpaqueKey (because it cannot be parsed into a globally-scoped identifier). By choosing the name ``refname``—which collides with none of the conventions above—the code signals clearly that this identifier is its own distinct thing.
177235

178236
Rationale
179237
*********
180238

181-
The four categories above cover the vast majority of identifiers that appear in Open edX Python code. Keeping the naming conventions to a small, well-defined set makes them easy to learn and apply consistently. The ``_pk`` / ``_key`` / ``_code`` suffixes were chosen because they are short, distinct from one another, and directly evocative of the kind of identifier they represent.
239+
The five categories above cover the vast majority of identifiers that appear in Open edX Python code. Keeping the naming conventions to a small, well-defined set makes them easy to learn and apply consistently. The ``_pk`` / ``_key`` / ``_code`` / ``_uuid`` suffixes were chosen because they are short, distinct from one another, and directly evocative of the kind of identifier they represent.
182240

183-
The ``_key_string`` convention for serialized OpaqueKeys was chosen over alternatives like ``_key_str`` or ``_key_serialized`` for readability. The word "string" more clearly signals to a reader that the value is a plain string rather than a Python object.
241+
The ``_key_string`` and ``_uuid_string`` conventions were chosen over alternatives like ``_key_str`` or ``_uuid_str`` for readability. The word "string" more clearly signals to a reader that the value is a plain string rather than a Python object.
184242

185243
Backward Compatibility
186244
**********************

0 commit comments

Comments
 (0)