Skip to content

Commit

Permalink
Do huge refactoring/cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
bkis committed Aug 29, 2023
1 parent 7443951 commit d025e1b
Show file tree
Hide file tree
Showing 13 changed files with 59 additions and 138 deletions.
4 changes: 2 additions & 2 deletions Tekst-API/tekst/email/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ def send_email(
templates[key]
.format(
web_url=urljoin(str(_cfg.server_url), _cfg.web_path).strip("/"),
**_cfg.info.model_dump(by_alias=False, exclude_unset=False),
**to_user.model_dump(by_alias=False, exclude_unset=False),
**_cfg.info.model_dump(),
**to_user.model_dump(),
**kwargs,
)
.strip()
Expand Down
34 changes: 4 additions & 30 deletions Tekst-API/tekst/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,17 @@ class ModelBase(ModelTransformerMixin, BaseModel):
alias_generator=camelize, populate_by_name=True, from_attributes=True
)

def model_dump(self, **kwargs) -> dict[str, Any]:
"""Overrides model_dump() in Basemodel to set some custom defaults"""
data = super().model_dump(
exclude_unset=kwargs.pop("exclude_unset", True),
by_alias=kwargs.pop("by_alias", True),
**kwargs,
)
return data

def model_dump_json(self, **kwargs) -> str:
"""Overrides model_dump_json() in Basemodel to set some custom defaults"""
return super().model_dump_json(
exclude_unset=kwargs.pop("exclude_unset", True),
by_alias=kwargs.pop("by_alias", True),
**kwargs,
)


class DocumentBase(ModelTransformerMixin, Document):
"""Base model for all Tekst ODM models"""

# model_config = None

def __init__(self, *args, **kwargs):
super().__init__(*args, **decamelize(kwargs))

def model_dump(
self, rename_id: bool = True, camelize_keys: bool = True, **kwargs
) -> dict[str, Any]:
"""Overrides model_dump() in Basemodel to set some custom defaults"""
data = super().model_dump(
exclude_unset=kwargs.pop("exclude_unset", True),
**kwargs,
)
if rename_id and "_id" in data:
data["id"] = data.pop("_id")
return camelize(data) if camelize_keys else data
def model_dump(self, camelize_keys: bool = False, **kwargs) -> dict[str, Any]:
if camelize_keys:
return camelize(super().model_dump(**kwargs))
return super().model_dump(**kwargs)

def restricted_fields(self, user_id: str = None) -> dict:
"""
Expand Down
7 changes: 2 additions & 5 deletions Tekst-API/tekst/routers/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,13 @@ async def get_unit_siblings(
NodeDocument.parent_id == parent_node_id,
).to_list()

units = await UnitBaseDocument.find(
unit_docs = await UnitBaseDocument.find(
UnitBaseDocument.layer_id == layer_id,
In(UnitBaseDocument.node_id, [node.id for node in nodes]),
with_children=True,
).to_list()

# calling model_dump(rename_id=True) on these models here makes sure they have
# "id" instead of "_id", because we're not using a proper read model here
# that could take care of that automatically (as we don't know the exact type)
return [unit.model_dump(rename_id=True) for unit in units]
return [unit_doc.model_dump(camelize_keys=True) for unit_doc in unit_docs]


@router.get(
Expand Down
17 changes: 6 additions & 11 deletions Tekst-API/tekst/routers/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ async def create_layer(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Layer refers to non-existent text '{layer.text_id}'",
)
uid = user.id if user else "no_id"
if uid != layer.owner_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Layer's owner ID doesn't match requesting user's ID",
)
return await layer_document_model(**layer.model_dump()).create()
# force some values on creation
layer.owner_id = user.id
layer.proposed = False
layer.public = False
return await layer_document_model.model_from(layer).create()

return create_layer

Expand Down Expand Up @@ -186,12 +184,9 @@ async def find_layers(
.to_list()
)

# calling model_dump(rename_id=True) on these models makes sure they have
# "id" instead of "_id", because we're not using a proper read model here
# that could take care of that automatically (as we don't know the exact type)
uid = user and user.id
return [
layer_doc.model_dump(rename_id=True, exclude=layer_doc.restricted_fields(uid))
layer_doc.model_dump(camelize_keys=True, exclude=layer_doc.restricted_fields(uid))
for layer_doc in layer_docs
]

Expand Down
2 changes: 1 addition & 1 deletion Tekst-API/tekst/routers/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def create_node(su: SuperuserDep, node: NodeCreate) -> NodeRead:
detail="Conflict with existing node",
)
# all fine
return await NodeDocument(**node.model_dump()).create()
return await NodeDocument.model_from(node).create()


@router.get("", response_model=list[NodeRead], status_code=status.HTTP_200_OK)
Expand Down
2 changes: 1 addition & 1 deletion Tekst-API/tekst/routers/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async def get_public_user_info(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User '{username_or_id}' does not exist",
)
return UserReadPublic(
return dict(
username=user.username,
**user.model_dump(
include={decamelize(field): True for field in user.public_fields}
Expand Down
7 changes: 1 addition & 6 deletions Tekst-API/tekst/routers/texts.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def create_text(su: SuperuserDep, text: TextCreate) -> TextRead:
status_code=status.HTTP_409_CONFLICT,
detail="An equal text already exists (same title or slug)",
)
return await TextDocument(**text.model_dump()).create()
return await TextDocument.model_from(text).create()


# @router.post(
Expand Down Expand Up @@ -308,10 +308,5 @@ async def update_text(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Text {text_id} doesn't exist",
)
# if updates.slug and updates.slug != text.slug:
# raise HTTPException(
# status_code=status.HTTP_400_BAD_REQUEST,
# detail="Text slug cannot be changed",
# )
await text.apply(updates.model_dump(exclude_unset=True))
return await TextDocument.get(text_id)
9 changes: 3 additions & 6 deletions Tekst-API/tekst/routers/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async def create_unit(unit: unit_create_model, user: UserDep) -> unit_read_model
status_code=status.HTTP_409_CONFLICT,
detail="The properties of this unit conflict with another unit",
)
return await unit_document_model(**unit.model_dump()).create()
return await unit_document_model.model_from(unit).create()

return create_unit

Expand Down Expand Up @@ -211,7 +211,7 @@ async def find_units(
with_children=True,
).to_list()

units = (
unit_docs = (
await UnitBaseDocument.find(
In(UnitBaseDocument.layer_id, layer_ids) if layer_ids else {},
In(UnitBaseDocument.node_id, node_ids) if node_ids else {},
Expand All @@ -222,7 +222,4 @@ async def find_units(
.to_list()
)

# calling model_dump(rename_id=True) on these models here makes sure they have
# "id" instead of "_id", because we're not using a proper read model here
# that could take care of that automatically (as we don't know the exact type)
return [unit.model_dump(rename_id=True) for unit in units]
return [unit_doc.model_dump(camelize_keys=True) for unit_doc in unit_docs]
8 changes: 4 additions & 4 deletions Tekst-API/tekst/sample_data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ async def _create_sample_unit(
):
# get node this unit belongs to
node = await NodeDocument.find(
NodeDocument.text_id == PydanticObjectId(layer_data.get("textId", "")),
NodeDocument.text_id == PydanticObjectId(layer_data.get("text_id")),
NodeDocument.level == layer_data.get("level", -1),
NodeDocument.position == unit_data.get("sampleNodePosition", -1),
NodeDocument.position == unit_data.get("sample_node_position", -1),
).first_or_none()
if not node:
log.error(f"Could not find target node for unit {unit_data}")
Expand All @@ -39,10 +39,10 @@ async def _create_sample_unit(

async def _create_sample_layers(text_slug: str, text_id: str):
for layer_data in LAYERS.get(text_slug, []):
layer_type = _layer_types.get(layer_data.get("layerType"))
layer_type = _layer_types.get(layer_data.get("layer_type"))
layer_doc_model = layer_type.get_layer_model().get_document_model()
if not layer_doc_model:
raise RuntimeError(f"Layer type {layer_data.get('layerType')} not found.")
raise RuntimeError(f"Layer type {layer_data.get('layer_type')} not found.")
layer_doc = layer_doc_model(text_id=text_id, **layer_data)
await layer_doc.create()
# units
Expand Down
64 changes: 32 additions & 32 deletions Tekst-API/tekst/sample_data/_sample_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
{"locale": "deDE", "label": "Strophe"},
],
],
"accentColor": "#D43A35",
"locDelim": ".",
"defaultLevel": 2,
"labeledLocation": False,
"isActive": False,
"accent_color": "#D43A35",
"loc_delim": ".",
"default_level": 2,
"labeled_location": False,
"is_active": False,
},
"nodes": [
{
Expand Down Expand Up @@ -137,9 +137,9 @@
],
],
"accent_color": "#43895F",
"locDelim": " > ",
"defaultLevel": 1,
"isActive": True,
"loc_delim": " > ",
"default_level": 1,
"is_active": True,
},
"nodes": [
{
Expand Down Expand Up @@ -232,17 +232,17 @@
{
"title": "Van Nooten & Holland",
"level": 2,
"layerType": "plaintext",
"layer_type": "plaintext",
"public": True,
"units": [
{"sampleNodePosition": 0, "text": "Foo Bar"},
{"sampleNodePosition": 1, "text": "Foo Bar"},
{"sampleNodePosition": 2, "text": "Foo Bar"},
{"sampleNodePosition": 3, "text": "Foo Bar"},
{"sampleNodePosition": 4, "text": "Foo Bar"},
{"sampleNodePosition": 5, "text": "Foo Bar"},
{"sampleNodePosition": 6, "text": "Foo Bar"},
{"sampleNodePosition": 7, "text": "Foo Bar"},
{"sample_node_position": 0, "text": "Foo Bar"},
{"sample_node_position": 1, "text": "Foo Bar"},
{"sample_node_position": 2, "text": "Foo Bar"},
{"sample_node_position": 3, "text": "Foo Bar"},
{"sample_node_position": 4, "text": "Foo Bar"},
{"sample_node_position": 5, "text": "Foo Bar"},
{"sample_node_position": 6, "text": "Foo Bar"},
{"sample_node_position": 7, "text": "Foo Bar"},
],
"meta": {"author": "Van Nooten & Holland", "year": "1995"},
"comment": "This is\na comment\nwith line breaks.",
Expand All @@ -252,55 +252,55 @@
{
"title": "Originalfassung",
"level": 1,
"layerType": "plaintext",
"layer_type": "plaintext",
"public": True,
"units": [
{
"sampleNodePosition": 0,
"sample_node_position": 0,
"text": "Fuchs, du hast die Gans gestohlen,",
},
{
"sampleNodePosition": 1,
"sample_node_position": 1,
"text": "|: gib sie wieder her! :|",
},
{
"sampleNodePosition": 2,
"sample_node_position": 2,
"text": "|: Sonst wird sie der Jäger holen",
},
{
"sampleNodePosition": 3,
"sample_node_position": 3,
"text": "mit dem Schießgewehr. :|",
},
{
"sampleNodePosition": 4,
"sample_node_position": 4,
"text": "Seine große, lange Flinte",
},
{
"sampleNodePosition": 5,
"sample_node_position": 5,
"text": "|: schießt auf dich den Schrot, :|",
},
{
"sampleNodePosition": 6,
"sample_node_position": 6,
"text": "|: dass dich färbt die rote Tinte",
},
{
"sampleNodePosition": 7,
"sample_node_position": 7,
"text": "und dann bist du tot. :|",
},
{
"sampleNodePosition": 8,
"sample_node_position": 8,
"text": "Liebes Füchslein, lass dir raten,",
},
{
"sampleNodePosition": 9,
"sample_node_position": 9,
"text": "|: sei doch nur kein Dieb; :|",
},
{
"sampleNodePosition": 10,
"sample_node_position": 10,
"text": "|: nimm, du brauchst nicht Gänsebraten,",
},
{
"sampleNodePosition": 11,
"sample_node_position": 11,
"text": "mit der Maus vorlieb. :|",
},
],
Expand All @@ -312,10 +312,10 @@
},
"comment": "This version includes repetition markers.",
"config": {
"deeplLinks": {
"deepl_links": {
"enabled": True,
"languages": ["EN", "FR"],
"sourceLanguage": "DE",
"source_language": "DE",
}
},
}
Expand Down
21 changes: 0 additions & 21 deletions Tekst-API/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,27 +174,6 @@ def _status_fail_msg(expected_status: int, response: Response) -> tuple[bool, st
return _status_fail_msg


# @pytest.fixture(scope="session")
# def json_compat() -> callable:
# """
# Returns a function that forces JSON encodability of the passed object.
# """

# def _json_compat(obj):
# if isinstance(obj, dict):
# return {str(k): _json_compat(v) for k, v in obj.items()}
# elif isinstance(obj, list):
# return [_json_compat(i) for i in obj]
# else:
# try:
# json.dumps(obj)
# except Exception:
# return str(obj)
# return obj

# return _json_compat


@pytest.fixture(autouse=True)
def disable_network_calls(monkeypatch):
"""Prevents outside network access while testing"""
Expand Down
3 changes: 2 additions & 1 deletion Tekst-API/tests/integration/test_api_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ async def test_create_layer_with_forged_owner_id(
"ownerId": "643d3cdc21efd6c46ae1527e",
}
resp = await test_client.post(endpoint, json=payload, cookies=session_cookie)
assert resp.status_code == 400, status_fail_msg(400, resp)
assert resp.status_code == 201, status_fail_msg(201, resp)
assert resp.json()["ownerId"] != payload["ownerId"]


@pytest.mark.anyio
Expand Down
Loading

0 comments on commit d025e1b

Please sign in to comment.