Skip to content

Commit

Permalink
Stop models converting to camelCase (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
callumforrester authored May 24, 2023
1 parent d31287f commit 51a4cf2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 22 deletions.
26 changes: 26 additions & 0 deletions docs/developer/explanations/decisions/0003-api-case.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
2. API Model Case
=================

Date: 2023-05-23

Status
------

Accepted

Context
-------

Considering whether keys in JSON blobs from the API should be in snake_case or camelCase.
This includes plan parameters which may be user-defined.

Decision
--------

The priority is not to confuse users, so we will not alias any field names defined in Python.

Consequences
------------

Most code will be written with pep8 enforcers which means most field names will be snake_case.
Some user defined ones may differ.
9 changes: 2 additions & 7 deletions src/blueapi/utils/base_model.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
from pydantic import BaseConfig, BaseModel, Extra


def _to_camel(string: str) -> str:
words = string.split("_")
return words[0] + "".join(word.capitalize() for word in words[1:])


class BlueapiModelConfig(BaseConfig):
"""
Pydantic config for blueapi API models with
common config.
"""

alias_generator = _to_camel
extra = Extra.forbid
allow_population_by_field_name = True


class BlueapiPlanModelConfig(BlueapiModelConfig):
class BlueapiPlanModelConfig(BaseConfig):
"""
Pydantic config for plan parameters.
Includes arbitrary type config so that devices
Expand All @@ -27,6 +21,7 @@ class BlueapiPlanModelConfig(BlueapiModelConfig):
from the context.
"""

extra = Extra.forbid
arbitrary_types_allowed = True
validate_all = True

Expand Down
12 changes: 11 additions & 1 deletion tests/core/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest
from bluesky.protocols import Descriptor, Movable, Readable, Reading, SyncOrAsync
from ophyd.sim import SynAxis, SynGauss
from pydantic import parse_obj_as
from pydantic import ValidationError, parse_obj_as

from blueapi.config import EnvironmentConfig, Source, SourceKind
from blueapi.core import (
Expand Down Expand Up @@ -329,3 +329,13 @@ def test_nested_str_default(
assert parse_obj_as(model, {}).m == [sim_motor] # type: ignore
empty_context.device(alt_motor)
assert parse_obj_as(model, {"m": [ALT_MOTOR_NAME]}).m == [alt_motor] # type: ignore


def test_plan_models_not_auto_camelcased(empty_context: BlueskyContext) -> None:
def a_plan(foo_bar: int, baz: str) -> MsgGenerator:
if False:
yield

empty_context.plan(a_plan)
with pytest.raises(ValidationError):
empty_context.plans[a_plan.__name__].model(fooBar=1, baz="test")
4 changes: 2 additions & 2 deletions tests/service/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class MyDevice:

def test_create_task(handler: Handler, client: TestClient) -> None:
response = client.post("/tasks", json=_TASK.dict())
task_id = response.json()["taskId"]
task_id = response.json()["task_id"]

pending = handler.worker.get_pending_task(task_id)
assert pending is not None
Expand All @@ -87,7 +87,7 @@ def test_create_task(handler: Handler, client: TestClient) -> None:

def test_put_plan_begins_task(handler: Handler, client: TestClient) -> None:
response = client.post("/tasks", json=_TASK.dict())
task_id = response.json()["taskId"]
task_id = response.json()["task_id"]

task_json = {"task_id": task_id}
client.put("/worker/task", json=task_json)
Expand Down
12 changes: 0 additions & 12 deletions tests/utils/test_base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,3 @@ def test_snake_case_constructor() -> None:
hello="hello",
hello_world="hello world",
)


def test_camel_case_parsing() -> None:
assert FooBar.parse_obj(
{
"hello": "hello",
"helloWorld": "hello world",
}
) == FooBar(
hello="hello",
hello_world="hello world",
)

0 comments on commit 51a4cf2

Please sign in to comment.