Skip to content

Commit 93852c3

Browse files
authored
Merge branch 'master' into feature/on-share-project-email
2 parents bcc714b + 11a7e7b commit 93852c3

File tree

19 files changed

+383
-220
lines changed

19 files changed

+383
-220
lines changed

packages/common-library/src/common_library/json_serialization.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
""" Helpers for json serialization
2-
- built-in json-like API
3-
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
1+
"""Helpers for json serialization
2+
- built-in json-like API
3+
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
44
"""
55

66
import datetime
@@ -118,6 +118,28 @@ def pydantic_encoder(obj: Any) -> Any:
118118
raise TypeError(msg)
119119

120120

121+
def representation_encoder(obj: Any):
122+
"""
123+
A fallback encoder that uses `pydantic_encoder` to serialize objects.
124+
If serialization fails, it falls back to using `str(obj)`.
125+
126+
This is practical for representation purposes, such as logging or debugging.
127+
128+
Example:
129+
>>> from common_library.json_serialization import json_dumps, representation_encoder
130+
>>> class CustomObject:
131+
... def __str__(self):
132+
... return "CustomObjectRepresentation"
133+
>>> obj = CustomObject()
134+
>>> json_dumps(obj, default=representation_encoder)
135+
'"CustomObjectRepresentation"'
136+
"""
137+
try:
138+
return pydantic_encoder(obj)
139+
except TypeError:
140+
return str(obj)
141+
142+
121143
def json_dumps(
122144
obj: Any,
123145
*,

packages/common-library/tests/test_json_serialization.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SeparatorTuple,
1414
json_dumps,
1515
json_loads,
16+
representation_encoder,
1617
)
1718
from faker import Faker
1819
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, HttpUrl, TypeAdapter
@@ -110,3 +111,25 @@ class M(BaseModel):
110111
http_url=faker.url(),
111112
)
112113
json_dumps(obj)
114+
115+
116+
def test_json_dumps_with_representation_encoder():
117+
class CustomObject:
118+
def __str__(self):
119+
return "CustomObjectRepresentation"
120+
121+
class SomeModel(BaseModel):
122+
x: int
123+
124+
obj = {
125+
"custom": CustomObject(),
126+
"some": SomeModel(x=42),
127+
}
128+
129+
# Using representation_encoder as the default encoder
130+
result = json_dumps(obj, default=representation_encoder, indent=1)
131+
132+
assert (
133+
result
134+
== '{\n "custom": "CustomObjectRepresentation",\n "some": {\n "x": 42\n }\n}'
135+
)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""add indexes
2+
3+
Revision ID: cf8f743fd0b7
4+
Revises: 48604dfdc5f4
5+
Create Date: 2025-04-04 09:46:38.853675+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision = "cf8f743fd0b7"
14+
down_revision = "48604dfdc5f4"
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_index(
22+
"idx_project_to_groups_gid", "project_to_groups", ["gid"], unique=False
23+
)
24+
op.create_index(
25+
"idx_projects_last_change_date_desc",
26+
"projects",
27+
["last_change_date"],
28+
unique=False,
29+
postgresql_using="btree",
30+
postgresql_ops={"last_change_date": "DESC"},
31+
)
32+
op.create_index(
33+
"ix_projects_partial_type",
34+
"projects",
35+
["type"],
36+
unique=False,
37+
postgresql_where=sa.text("type = 'TEMPLATE'"),
38+
)
39+
op.create_index(
40+
"idx_project_to_folders_project_uuid",
41+
"projects_to_folders",
42+
["project_uuid"],
43+
unique=False,
44+
)
45+
op.create_index(
46+
"idx_project_to_folders_user_id",
47+
"projects_to_folders",
48+
["user_id"],
49+
unique=False,
50+
)
51+
op.create_index(
52+
"idx_projects_to_products_product_name",
53+
"projects_to_products",
54+
["product_name"],
55+
unique=False,
56+
)
57+
op.create_index(
58+
"idx_workspaces_access_rights_gid",
59+
"workspaces_access_rights",
60+
["gid"],
61+
unique=False,
62+
)
63+
# ### end Alembic commands ###
64+
65+
66+
def downgrade():
67+
# ### commands auto generated by Alembic - please adjust! ###
68+
op.drop_index(
69+
"idx_workspaces_access_rights_gid", table_name="workspaces_access_rights"
70+
)
71+
op.drop_index(
72+
"idx_projects_to_products_product_name", table_name="projects_to_products"
73+
)
74+
op.drop_index("idx_project_to_folders_user_id", table_name="projects_to_folders")
75+
op.drop_index(
76+
"idx_project_to_folders_project_uuid", table_name="projects_to_folders"
77+
)
78+
op.drop_index(
79+
"ix_projects_partial_type",
80+
table_name="projects",
81+
postgresql_where=sa.text("type = 'TEMPLATE'"),
82+
)
83+
op.drop_index(
84+
"idx_projects_last_change_date_desc",
85+
table_name="projects",
86+
postgresql_using="btree",
87+
postgresql_ops={"last_change_date": "DESC"},
88+
)
89+
op.drop_index("idx_project_to_groups_gid", table_name="project_to_groups")
90+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/project_to_groups.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,5 @@
6060
column_created_datetime(timezone=True),
6161
column_modified_datetime(timezone=True),
6262
sa.UniqueConstraint("project_uuid", "gid"),
63+
sa.Index("idx_project_to_groups_gid", "gid"),
6364
)

packages/postgres-database/src/simcore_postgres_database/models/projects.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
""" Projects table
1+
"""Projects table"""
22

3-
"""
43
import enum
54

65
import sqlalchemy as sa
@@ -171,6 +170,20 @@ class ProjectType(enum.Enum):
171170
server_default=sa.text("'{}'::jsonb"),
172171
doc="DEPRECATED: Read/write/delete access rights of each group (gid) on this project",
173172
),
173+
### INDEXES ----------------------------
174+
sa.Index(
175+
"idx_projects_last_change_date_desc",
176+
"last_change_date",
177+
postgresql_using="btree",
178+
postgresql_ops={"last_change_date": "DESC"},
179+
),
180+
)
181+
182+
# We define the partial index
183+
sa.Index(
184+
"ix_projects_partial_type",
185+
projects.c.type,
186+
postgresql_where=(projects.c.type == ProjectType.TEMPLATE),
174187
)
175188

176189

packages/postgres-database/src/simcore_postgres_database/models/projects_to_folders.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@
4242
column_created_datetime(timezone=True),
4343
column_modified_datetime(timezone=True),
4444
sa.UniqueConstraint("project_uuid", "folder_id", "user_id"),
45+
sa.Index("idx_project_to_folders_project_uuid", "project_uuid"),
46+
sa.Index("idx_project_to_folders_user_id", "user_id"),
4547
)

packages/postgres-database/src/simcore_postgres_database/models/projects_to_products.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@
3434
column_created_datetime(timezone=False),
3535
column_modified_datetime(timezone=False),
3636
sa.UniqueConstraint("project_uuid", "product_name"),
37+
sa.Index("idx_projects_to_products_product_name", "product_name"),
3738
)

packages/postgres-database/src/simcore_postgres_database/models/workspaces_access_rights.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,5 @@
5757
column_created_datetime(timezone=True),
5858
column_modified_datetime(timezone=True),
5959
sa.UniqueConstraint("workspace_id", "gid"),
60+
sa.Index("idx_workspaces_access_rights_gid", "gid"),
6061
)

packages/service-library/src/servicelib/logging_errors.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import logging
2-
from pprint import pformat
32
from typing import Any, TypedDict
43

54
from common_library.error_codes import ErrorCodeStr
65
from common_library.errors_classes import OsparcErrorMixin
6+
from common_library.json_serialization import json_dumps, representation_encoder
77

88
from .logging_utils import LogExtra, get_log_record_extra
99

@@ -27,14 +27,15 @@ def create_troubleshotting_log_message(
2727
error_context -- Additional context surrounding the exception, such as environment variables or function-specific data. This can be derived from exc.error_context() (relevant when using the OsparcErrorMixin)
2828
tip -- Helpful suggestions or possible solutions explaining why the error may have occurred and how it could potentially be resolved
2929
"""
30-
debug_data = pformat(
30+
debug_data = json_dumps(
3131
{
3232
"exception_type": f"{type(error)}",
3333
"exception_details": f"{error}",
3434
"error_code": error_code,
35-
"context": pformat(error_context, indent=1),
35+
"context": error_context,
3636
"tip": tip,
3737
},
38+
default=representation_encoder,
3839
indent=1,
3940
)
4041

@@ -82,7 +83,7 @@ def create_troubleshotting_log_kwargs(
8283
error=error,
8384
error_code=error_code,
8485
error_context=context,
85-
tip=tip,
86+
tip=tip or getattr(error, "tip", None),
8687
)
8788

8889
return {

scripts/maintenance/computational-clusters/autoscaled_monitor/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ def cancel_jobs(
177177
@app.command()
178178
def trigger_cluster_termination(
179179
user_id: Annotated[int, typer.Option(help="the user ID")],
180-
wallet_id: Annotated[int, typer.Option(help="the wallet ID")],
180+
wallet_id: Annotated[
181+
Optional[int | None], # noqa: UP007 # typer does not understand | syntax
182+
typer.Option(help="the wallet ID"),
183+
] = None,
184+
*,
181185
force: Annotated[bool, typer.Option(help="will not ask for confirmation")] = False,
182186
) -> None:
183187
"""this will set the Heartbeat tag on the primary machine to 1 hour, thus ensuring the

0 commit comments

Comments
 (0)