Reformatting everything with black

This commit is contained in:
Ian Roddis committed 2026-07-14 14:36:49 -03:00
1 parent 7bd07f084a
commit 8185f1f8b3
48 files changed
+996 -344

No files matched your search

@@ -4,15 +4,15 @@ Revision ID: 0c3d1a5cff1f
Revises: 5b6a7817b408 Revises: 5b6a7817b408
Create Date: 2026-07-02 12:22:24.106795 Create Date: 2026-07-02 12:22:24.106795
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '0c3d1a5cff1f' revision: str = "0c3d1a5cff1f"
down_revision: Union[str, None] = '5b6a7817b408' down_revision: Union[str, None] = "5b6a7817b408"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: 5b6a7817b408
Revises: 7be5ef599d3f Revises: 7be5ef599d3f
Create Date: 2026-07-02 11:57:13.095417 Create Date: 2026-07-02 11:57:13.095417
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '5b6a7817b408' revision: str = "5b6a7817b408"
down_revision: Union[str, None] = '7be5ef599d3f' down_revision: Union[str, None] = "7be5ef599d3f"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: 70e48bda8829
Revises: c7882cedc9ae Revises: c7882cedc9ae
Create Date: 2026-07-02 13:54:58.335394 Create Date: 2026-07-02 13:54:58.335394
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '70e48bda8829' revision: str = "70e48bda8829"
down_revision: Union[str, None] = 'c7882cedc9ae' down_revision: Union[str, None] = "c7882cedc9ae"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: 7be5ef599d3f
Revises: b60d0aa3ceda Revises: b60d0aa3ceda
Create Date: 2026-06-25 15:29:20.685050 Create Date: 2026-06-25 15:29:20.685050
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '7be5ef599d3f' revision: str = "7be5ef599d3f"
down_revision: Union[str, None] = 'b60d0aa3ceda' down_revision: Union[str, None] = "b60d0aa3ceda"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: 8bc411b5814e
Revises: 0c3d1a5cff1f Revises: 0c3d1a5cff1f
Create Date: 2026-07-02 12:37:59.457033 Create Date: 2026-07-02 12:37:59.457033
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '8bc411b5814e' revision: str = "8bc411b5814e"
down_revision: Union[str, None] = '0c3d1a5cff1f' down_revision: Union[str, None] = "0c3d1a5cff1f"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,44 +4,49 @@ Revision ID: a192abbf4091
Revises: 627d4460a94f Revises: 627d4460a94f
Create Date: 2026-06-25 10:32:11.398529 Create Date: 2026-06-25 10:32:11.398529
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'a192abbf4091' revision: str = "a192abbf4091"
down_revision: Union[str, None] = '627d4460a94f' down_revision: Union[str, None] = "627d4460a94f"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('manufacturers', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "manufacturers",
sa.Column('name', sa.String(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('url', sa.String(), nullable=False), sa.Column("name", sa.String(), nullable=False),
sa.Column('logo_url', sa.String(), nullable=True), sa.Column("url", sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id') sa.Column("logo_url", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
) )
with op.batch_alter_table('unc_threads', schema=None) as batch_op: with op.batch_alter_table("unc_threads", schema=None) as batch_op:
batch_op.alter_column('series', batch_op.alter_column(
"series",
existing_type=sa.VARCHAR(length=8), existing_type=sa.VARCHAR(length=8),
type_=sa.Enum('UNC', 'UNF', 'UNEF', 'UN', 'UNS', name='uncseries'), type_=sa.Enum("UNC", "UNF", "UNEF", "UN", "UNS", name="uncseries"),
existing_nullable=False) existing_nullable=False,
)
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('unc_threads', schema=None) as batch_op: with op.batch_alter_table("unc_threads", schema=None) as batch_op:
batch_op.alter_column('series', batch_op.alter_column(
existing_type=sa.Enum('UNC', 'UNF', 'UNEF', 'UN', 'UNS', name='uncseries'), "series",
existing_type=sa.Enum("UNC", "UNF", "UNEF", "UN", "UNS", name="uncseries"),
type_=sa.VARCHAR(length=8), type_=sa.VARCHAR(length=8),
existing_nullable=False) existing_nullable=False,
)
op.drop_table('manufacturers') op.drop_table("manufacturers")
# ### end Alembic commands ### # ### end Alembic commands ###
@@ -4,33 +4,36 @@ Revision ID: a52e7ca7a925
Revises: a192abbf4091 Revises: a192abbf4091
Create Date: 2026-06-25 10:35:40.227253 Create Date: 2026-06-25 10:35:40.227253
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'a52e7ca7a925' revision: str = "a52e7ca7a925"
down_revision: Union[str, None] = 'a192abbf4091' down_revision: Union[str, None] = "a192abbf4091"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('scope_models', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "scope_models",
sa.Column('name', sa.String(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('manufacturer_id', sa.Integer(), nullable=False), sa.Column("name", sa.String(), nullable=False),
sa.Column('url', sa.String(), nullable=True), sa.Column("manufacturer_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['manufacturer_id'], ['manufacturers.id'], ondelete='RESTRICT'), sa.Column("url", sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id') sa.ForeignKeyConstraint(
["manufacturer_id"], ["manufacturers.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('scope_models') op.drop_table("scope_models")
# ### end Alembic commands ### # ### end Alembic commands ###
@@ -4,15 +4,15 @@ Revision ID: adf4a5b9493e
Revises: 70e48bda8829 Revises: 70e48bda8829
Create Date: 2026-07-02 14:03:23.561253 Create Date: 2026-07-02 14:03:23.561253
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'adf4a5b9493e' revision: str = "adf4a5b9493e"
down_revision: Union[str, None] = '70e48bda8829' down_revision: Union[str, None] = "70e48bda8829"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: b60d0aa3ceda
Revises: fd5379986da4 Revises: fd5379986da4
Create Date: 2026-06-25 11:29:20.420056 Create Date: 2026-06-25 11:29:20.420056
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'b60d0aa3ceda' revision: str = "b60d0aa3ceda"
down_revision: Union[str, None] = 'fd5379986da4' down_revision: Union[str, None] = "fd5379986da4"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: bfbce324cd5f
Revises: 8bc411b5814e Revises: 8bc411b5814e
Create Date: 2026-07-02 12:46:27.493942 Create Date: 2026-07-02 12:46:27.493942
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'bfbce324cd5f' revision: str = "bfbce324cd5f"
down_revision: Union[str, None] = '8bc411b5814e' down_revision: Union[str, None] = "8bc411b5814e"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,15 +4,15 @@ Revision ID: c7882cedc9ae
Revises: bfbce324cd5f Revises: bfbce324cd5f
Create Date: 2026-07-02 13:31:57.064381 Create Date: 2026-07-02 13:31:57.064381
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'c7882cedc9ae' revision: str = "c7882cedc9ae"
down_revision: Union[str, None] = 'bfbce324cd5f' down_revision: Union[str, None] = "bfbce324cd5f"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -4,31 +4,32 @@ Revision ID: e23aa8dd2536
Revises: a52e7ca7a925 Revises: a52e7ca7a925
Create Date: 2026-06-25 10:49:56.463466 Create Date: 2026-06-25 10:49:56.463466
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'e23aa8dd2536' revision: str = "e23aa8dd2536"
down_revision: Union[str, None] = 'a52e7ca7a925' down_revision: Union[str, None] = "a52e7ca7a925"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('part_types', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "part_types",
sa.Column('name', sa.String(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.Column("name", sa.String(), nullable=False),
sa.UniqueConstraint('name') sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("name"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('part_types') op.drop_table("part_types")
# ### end Alembic commands ### # ### end Alembic commands ###
@@ -4,15 +4,15 @@ Revision ID: fd5379986da4
Revises: e23aa8dd2536 Revises: e23aa8dd2536
Create Date: 2026-06-25 11:06:13.028067 Create Date: 2026-06-25 11:06:13.028067
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'fd5379986da4' revision: str = "fd5379986da4"
down_revision: Union[str, None] = 'e23aa8dd2536' down_revision: Union[str, None] = "e23aa8dd2536"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
+12 -3
View File
@@ -35,7 +35,10 @@ async def create_attachment_point_name(
): ):
apn = await apn_crud.create(db, name=payload.name) apn = await apn_crud.create(db, name=payload.name)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="AttachmentPointName", db,
actor=actor,
action="create",
resource_type="AttachmentPointName",
resource_id=apn.id, resource_id=apn.id,
description=audit_crud.describe_create("AttachmentPointName", apn.id, apn.name), description=audit_crud.describe_create("AttachmentPointName", apn.id, apn.name),
) )
@@ -67,7 +70,10 @@ async def update_attachment_point_name(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
apn = await apn_crud.update(db, apn, changes) apn = await apn_crud.update(db, apn, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="AttachmentPointName", db,
actor=actor,
action="update",
resource_type="AttachmentPointName",
resource_id=apn.id, resource_id=apn.id,
description=audit_crud.describe_update("AttachmentPointName", apn.id, changes), description=audit_crud.describe_update("AttachmentPointName", apn.id, changes),
) )
@@ -86,7 +92,10 @@ async def delete_attachment_point_name(
name = apn.name name = apn.name
await apn_crud.delete(db, apn) await apn_crud.delete(db, apn)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="AttachmentPointName", db,
actor=actor,
action="delete",
resource_type="AttachmentPointName",
resource_id=apn_id, resource_id=apn_id,
description=audit_crud.describe_delete("AttachmentPointName", apn_id, name), description=audit_crud.describe_delete("AttachmentPointName", apn_id, name),
) )
+18 -5
View File
@@ -40,7 +40,9 @@ async def list_attachment_points(
return await attachment_point_crud.list_all(db, skip=skip, limit=limit) return await attachment_point_crud.list_all(db, skip=skip, limit=limit)
@router.post("", response_model=AttachmentPointRead, status_code=status.HTTP_201_CREATED) @router.post(
"", response_model=AttachmentPointRead, status_code=status.HTTP_201_CREATED
)
async def create_attachment_point( async def create_attachment_point(
payload: AttachmentPointCreate, payload: AttachmentPointCreate,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
@@ -59,7 +61,10 @@ async def create_attachment_point(
) )
ap = await attachment_point_crud.create(db, **payload.model_dump()) ap = await attachment_point_crud.create(db, **payload.model_dump())
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="AttachmentPoint", db,
actor=actor,
action="create",
resource_type="AttachmentPoint",
resource_id=ap.id, resource_id=ap.id,
description=audit_crud.describe_create( description=audit_crud.describe_create(
"AttachmentPoint", ap.id, _connection_label(ap) "AttachmentPoint", ap.id, _connection_label(ap)
@@ -93,7 +98,10 @@ async def update_attachment_point(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
ap = await attachment_point_crud.update(db, ap, changes) ap = await attachment_point_crud.update(db, ap, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="AttachmentPoint", db,
actor=actor,
action="update",
resource_type="AttachmentPoint",
resource_id=ap.id, resource_id=ap.id,
description=audit_crud.describe_update("AttachmentPoint", ap.id, changes), description=audit_crud.describe_update("AttachmentPoint", ap.id, changes),
) )
@@ -112,7 +120,12 @@ async def delete_attachment_point(
label = _connection_label(ap) label = _connection_label(ap)
await attachment_point_crud.delete(db, ap) await attachment_point_crud.delete(db, ap)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="AttachmentPoint", db,
actor=actor,
action="delete",
resource_type="AttachmentPoint",
resource_id=attachment_point_id, resource_id=attachment_point_id,
description=audit_crud.describe_delete("AttachmentPoint", attachment_point_id, label), description=audit_crud.describe_delete(
"AttachmentPoint", attachment_point_id, label
),
) )
+22 -6
View File
@@ -44,9 +44,14 @@ async def create_augmented_part(
sub_part_type_id=payload.sub_part_type_id, sub_part_type_id=payload.sub_part_type_id,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="AugmentedPart", db,
actor=actor,
action="create",
resource_type="AugmentedPart",
resource_id=ap.id, resource_id=ap.id,
description=audit_crud.describe_create("AugmentedPart", ap.id, ap.sku or ap.description), description=audit_crud.describe_create(
"AugmentedPart", ap.id, ap.sku or ap.description
),
) )
return ap return ap
@@ -58,7 +63,10 @@ async def sync_augmented_parts(
): ):
result = await augmented_part_sync.sync(db) result = await augmented_part_sync.sync(db)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="AugmentedPart", db,
actor=actor,
action="update",
resource_type="AugmentedPart",
resource_id=None, resource_id=None,
description=( description=(
f"Sync: {result.created} created, {result.deleted} deleted, " f"Sync: {result.created} created, {result.deleted} deleted, "
@@ -93,7 +101,10 @@ async def update_augmented_part(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
ap = await augmented_part_crud.update(db, ap, changes) ap = await augmented_part_crud.update(db, ap, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="AugmentedPart", db,
actor=actor,
action="update",
resource_type="AugmentedPart",
resource_id=ap.id, resource_id=ap.id,
description=audit_crud.describe_update("AugmentedPart", ap.id, changes), description=audit_crud.describe_update("AugmentedPart", ap.id, changes),
) )
@@ -112,7 +123,12 @@ async def delete_augmented_part(
label = ap.sku or ap.description label = ap.sku or ap.description
await augmented_part_crud.delete(db, ap) await augmented_part_crud.delete(db, ap)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="AugmentedPart", db,
actor=actor,
action="delete",
resource_type="AugmentedPart",
resource_id=augmented_part_id, resource_id=augmented_part_id,
description=audit_crud.describe_delete("AugmentedPart", augmented_part_id, label), description=audit_crud.describe_delete(
"AugmentedPart", augmented_part_id, label
),
) )
+27 -8
View File
@@ -25,7 +25,9 @@ async def list_clamp_connections(
return await clamp_connection_crud.list_all(db, skip=skip, limit=limit) return await clamp_connection_crud.list_all(db, skip=skip, limit=limit)
@router.post("", response_model=ClampConnectionRead, status_code=status.HTTP_201_CREATED) @router.post(
"", response_model=ClampConnectionRead, status_code=status.HTTP_201_CREATED
)
async def create_clamp_connection( async def create_clamp_connection(
payload: ClampConnectionCreate, payload: ClampConnectionCreate,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
@@ -39,11 +41,19 @@ async def create_clamp_connection(
max_in=payload.max_in, max_in=payload.max_in,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="ClampConnection", db,
actor=actor,
action="create",
resource_type="ClampConnection",
resource_id=cc.id, resource_id=cc.id,
description=audit_crud.describe_create( description=audit_crud.describe_create(
"ClampConnection", cc.id, "ClampConnection",
f"{cc.min_mm}–{cc.max_mm} mm" if cc.min_mm is not None else f"{cc.min_in}–{cc.max_in} in", cc.id,
(
f"{cc.min_mm}–{cc.max_mm} mm"
if cc.min_mm is not None
else f"{cc.min_in}–{cc.max_in} in"
),
), ),
) )
return cc return cc
@@ -74,7 +84,10 @@ async def update_clamp_connection(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
cc = await clamp_connection_crud.update(db, cc, changes) cc = await clamp_connection_crud.update(db, cc, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="ClampConnection", db,
actor=actor,
action="update",
resource_type="ClampConnection",
resource_id=cc.id, resource_id=cc.id,
description=audit_crud.describe_update("ClampConnection", cc.id, changes), description=audit_crud.describe_update("ClampConnection", cc.id, changes),
) )
@@ -91,12 +104,18 @@ async def delete_clamp_connection(
if cc is None: if cc is None:
raise HTTPException(status_code=404, detail="Clamp connection not found") raise HTTPException(status_code=404, detail="Clamp connection not found")
label = ( label = (
f"{cc.min_mm}–{cc.max_mm} mm" if cc.min_mm is not None f"{cc.min_mm}–{cc.max_mm} mm"
if cc.min_mm is not None
else f"{cc.min_in}–{cc.max_in} in" else f"{cc.min_in}–{cc.max_in} in"
) )
await clamp_connection_crud.delete(db, cc) await clamp_connection_crud.delete(db, cc)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="ClampConnection", db,
actor=actor,
action="delete",
resource_type="ClampConnection",
resource_id=clamp_connection_id, resource_id=clamp_connection_id,
description=audit_crud.describe_delete("ClampConnection", clamp_connection_id, label), description=audit_crud.describe_delete(
"ClampConnection", clamp_connection_id, label
),
) )
+26 -7
View File
@@ -51,7 +51,11 @@ async def update_config(
): ):
current = await config_crud.get(db) current = await config_crud.get(db)
# Keep existing password if not provided in the update # Keep existing password if not provided in the update
new_password = payload.password if payload.password is not None else (current.password if current else None) new_password = (
payload.password
if payload.password is not None
else (current.password if current else None)
)
config = await config_crud.upsert( config = await config_crud.upsert(
db, db,
odata_url=payload.odata_url, odata_url=payload.odata_url,
@@ -59,7 +63,10 @@ async def update_config(
password=new_password, password=new_password,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="ImportedPartConfig", db,
actor=actor,
action="update",
resource_type="ImportedPartConfig",
resource_id=1, resource_id=1,
description="Updated ImportedPart OData configuration", description="Updated ImportedPart OData configuration",
) )
@@ -81,10 +88,15 @@ async def refresh_imported_parts(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="OData URL is not configured.", detail="OData URL is not configured.",
) )
records = await odata_service.fetch(config.odata_url, config.username, config.password) records = await odata_service.fetch(
config.odata_url, config.username, config.password
)
created, updated = await imported_part_crud.upsert_bulk(db, records) created, updated = await imported_part_crud.upsert_bulk(db, records)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="ImportedPart", db,
actor=actor,
action="update",
resource_type="ImportedPart",
resource_id=None, resource_id=None,
description=f"OData sync: {created} created, {updated} updated ({len(records)} total records)", description=f"OData sync: {created} created, {updated} updated ({len(records)} total records)",
) )
@@ -99,7 +111,9 @@ async def get_imported_part(
): ):
part = await imported_part_crud.get(db, inventory_id) part = await imported_part_crud.get(db, inventory_id)
if part is None: if part is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found"
)
return part return part
@@ -111,10 +125,15 @@ async def delete_imported_part(
): ):
part = await imported_part_crud.get(db, inventory_id) part = await imported_part_crud.get(db, inventory_id)
if part is None: if part is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found"
)
await imported_part_crud.delete(db, part) await imported_part_crud.delete(db, part)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="ImportedPart", db,
actor=actor,
action="delete",
resource_type="ImportedPart",
resource_id=None, resource_id=None,
description=audit_crud.describe_delete("ImportedPart", 0, inventory_id), description=audit_crud.describe_delete("ImportedPart", 0, inventory_id),
) )
+24 -7
View File
@@ -34,9 +34,14 @@ async def create_iso_thread(
depth_mm=payload.depth_mm, depth_mm=payload.depth_mm,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="IsoThread", db,
actor=actor,
action="create",
resource_type="IsoThread",
resource_id=thread.id, resource_id=thread.id,
description=audit_crud.describe_create("IsoThread", thread.id, f"M{thread.diameter_mm}×{thread.pitch_mm}"), description=audit_crud.describe_create(
"IsoThread", thread.id, f"M{thread.diameter_mm}×{thread.pitch_mm}"
),
) )
return thread return thread
@@ -49,7 +54,9 @@ async def get_iso_thread(
): ):
thread = await iso_thread_crud.get(db, iso_thread_id) thread = await iso_thread_crud.get(db, iso_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found"
)
return thread return thread
@@ -62,11 +69,16 @@ async def update_iso_thread(
): ):
thread = await iso_thread_crud.get(db, iso_thread_id) thread = await iso_thread_crud.get(db, iso_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found"
)
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
thread = await iso_thread_crud.update(db, thread, changes) thread = await iso_thread_crud.update(db, thread, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="IsoThread", db,
actor=actor,
action="update",
resource_type="IsoThread",
resource_id=thread.id, resource_id=thread.id,
description=audit_crud.describe_update("IsoThread", thread.id, changes), description=audit_crud.describe_update("IsoThread", thread.id, changes),
) )
@@ -81,11 +93,16 @@ async def delete_iso_thread(
): ):
thread = await iso_thread_crud.get(db, iso_thread_id) thread = await iso_thread_crud.get(db, iso_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="ISO thread not found"
)
name = f"M{thread.diameter_mm}×{thread.pitch_mm}" name = f"M{thread.diameter_mm}×{thread.pitch_mm}"
await iso_thread_crud.delete(db, thread) await iso_thread_crud.delete(db, thread)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="IsoThread", db,
actor=actor,
action="delete",
resource_type="IsoThread",
resource_id=iso_thread_id, resource_id=iso_thread_id,
description=audit_crud.describe_delete("IsoThread", iso_thread_id, name), description=audit_crud.describe_delete("IsoThread", iso_thread_id, name),
) )
+26 -7
View File
@@ -6,7 +6,11 @@ from app.crud import audit as audit_crud
from app.crud import manufacturer as manufacturer_crud from app.crud import manufacturer as manufacturer_crud
from app.deps import require_permission from app.deps import require_permission
from app.models.user import User from app.models.user import User
from app.schemas.manufacturer import ManufacturerCreate, ManufacturerRead, ManufacturerUpdate from app.schemas.manufacturer import (
ManufacturerCreate,
ManufacturerRead,
ManufacturerUpdate,
)
router = APIRouter() router = APIRouter()
@@ -34,7 +38,10 @@ async def create_manufacturer(
logo_url=str(payload.logo_url) if payload.logo_url else None, logo_url=str(payload.logo_url) if payload.logo_url else None,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="Manufacturer", db,
actor=actor,
action="create",
resource_type="Manufacturer",
resource_id=m.id, resource_id=m.id,
description=audit_crud.describe_create("Manufacturer", m.id, m.name), description=audit_crud.describe_create("Manufacturer", m.id, m.name),
) )
@@ -49,7 +56,9 @@ async def get_manufacturer(
): ):
m = await manufacturer_crud.get(db, manufacturer_id) m = await manufacturer_crud.get(db, manufacturer_id)
if m is None: if m is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found"
)
return m return m
@@ -62,7 +71,9 @@ async def update_manufacturer(
): ):
m = await manufacturer_crud.get(db, manufacturer_id) m = await manufacturer_crud.get(db, manufacturer_id)
if m is None: if m is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found"
)
data = payload.model_dump(exclude_unset=True) data = payload.model_dump(exclude_unset=True)
if "url" in data and data["url"] is not None: if "url" in data and data["url"] is not None:
data["url"] = str(data["url"]) data["url"] = str(data["url"])
@@ -70,7 +81,10 @@ async def update_manufacturer(
data["logo_url"] = str(data["logo_url"]) data["logo_url"] = str(data["logo_url"])
m = await manufacturer_crud.update(db, m, data) m = await manufacturer_crud.update(db, m, data)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="Manufacturer", db,
actor=actor,
action="update",
resource_type="Manufacturer",
resource_id=m.id, resource_id=m.id,
description=audit_crud.describe_update("Manufacturer", m.id, data), description=audit_crud.describe_update("Manufacturer", m.id, data),
) )
@@ -85,11 +99,16 @@ async def delete_manufacturer(
): ):
m = await manufacturer_crud.get(db, manufacturer_id) m = await manufacturer_crud.get(db, manufacturer_id)
if m is None: if m is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Manufacturer not found"
)
name = m.name name = m.name
await manufacturer_crud.delete(db, m) await manufacturer_crud.delete(db, m)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="Manufacturer", db,
actor=actor,
action="delete",
resource_type="Manufacturer",
resource_id=manufacturer_id, resource_id=manufacturer_id,
description=audit_crud.describe_delete("Manufacturer", manufacturer_id, name), description=audit_crud.describe_delete("Manufacturer", manufacturer_id, name),
) )
+12 -3
View File
@@ -29,7 +29,10 @@ async def create_part_colour(
): ):
pc = await part_colour_crud.create(db, name=payload.name) pc = await part_colour_crud.create(db, name=payload.name)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="PartColour", db,
actor=actor,
action="create",
resource_type="PartColour",
resource_id=pc.id, resource_id=pc.id,
description=audit_crud.describe_create("PartColour", pc.id, pc.name), description=audit_crud.describe_create("PartColour", pc.id, pc.name),
) )
@@ -61,7 +64,10 @@ async def update_part_colour(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
pc = await part_colour_crud.update(db, pc, changes) pc = await part_colour_crud.update(db, pc, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="PartColour", db,
actor=actor,
action="update",
resource_type="PartColour",
resource_id=pc.id, resource_id=pc.id,
description=audit_crud.describe_update("PartColour", pc.id, changes), description=audit_crud.describe_update("PartColour", pc.id, changes),
) )
@@ -80,7 +86,10 @@ async def delete_part_colour(
name = pc.name name = pc.name
await part_colour_crud.delete(db, pc) await part_colour_crud.delete(db, pc)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="PartColour", db,
actor=actor,
action="delete",
resource_type="PartColour",
resource_id=part_colour_id, resource_id=part_colour_id,
description=audit_crud.describe_delete("PartColour", part_colour_id, name), description=audit_crud.describe_delete("PartColour", part_colour_id, name),
) )
+21 -6
View File
@@ -29,7 +29,10 @@ async def create_part_type(
): ):
pt = await part_type_crud.create(db, name=payload.name) pt = await part_type_crud.create(db, name=payload.name)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="PartType", db,
actor=actor,
action="create",
resource_type="PartType",
resource_id=pt.id, resource_id=pt.id,
description=audit_crud.describe_create("PartType", pt.id, pt.name), description=audit_crud.describe_create("PartType", pt.id, pt.name),
) )
@@ -44,7 +47,9 @@ async def get_part_type(
): ):
pt = await part_type_crud.get(db, part_type_id) pt = await part_type_crud.get(db, part_type_id)
if pt is None: if pt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found"
)
return pt return pt
@@ -57,11 +62,16 @@ async def update_part_type(
): ):
pt = await part_type_crud.get(db, part_type_id) pt = await part_type_crud.get(db, part_type_id)
if pt is None: if pt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found"
)
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
pt = await part_type_crud.update(db, pt, changes) pt = await part_type_crud.update(db, pt, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="PartType", db,
actor=actor,
action="update",
resource_type="PartType",
resource_id=pt.id, resource_id=pt.id,
description=audit_crud.describe_update("PartType", pt.id, changes), description=audit_crud.describe_update("PartType", pt.id, changes),
) )
@@ -76,11 +86,16 @@ async def delete_part_type(
): ):
pt = await part_type_crud.get(db, part_type_id) pt = await part_type_crud.get(db, part_type_id)
if pt is None: if pt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Part type not found"
)
name = pt.name name = pt.name
await part_type_crud.delete(db, pt) await part_type_crud.delete(db, pt)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="PartType", db,
actor=actor,
action="delete",
resource_type="PartType",
resource_id=part_type_id, resource_id=part_type_id,
description=audit_crud.describe_delete("PartType", part_type_id, name), description=audit_crud.describe_delete("PartType", part_type_id, name),
) )
+12 -3
View File
@@ -36,7 +36,10 @@ async def create_role(
permission_ids=payload.permission_ids, permission_ids=payload.permission_ids,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="Role", db,
actor=actor,
action="create",
resource_type="Role",
resource_id=role.id, resource_id=role.id,
description=audit_crud.describe_create("Role", role.id, role.name), description=audit_crud.describe_create("Role", role.id, role.name),
) )
@@ -80,7 +83,10 @@ async def update_role(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
role = await role_crud.update(db, role, changes) role = await role_crud.update(db, role, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="Role", db,
actor=actor,
action="update",
resource_type="Role",
resource_id=role.id, resource_id=role.id,
description=audit_crud.describe_update("Role", role.id, changes), description=audit_crud.describe_update("Role", role.id, changes),
) )
@@ -101,7 +107,10 @@ async def delete_role(
name = role.name name = role.name
await role_crud.delete(db, role) await role_crud.delete(db, role)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="Role", db,
actor=actor,
action="delete",
resource_type="Role",
resource_id=role_id, resource_id=role_id,
description=audit_crud.describe_delete("Role", role_id, name), description=audit_crud.describe_delete("Role", role_id, name),
) )
+57 -13
View File
@@ -1,22 +1,66 @@
from fastapi import APIRouter from fastapi import APIRouter
from app.api import attachment_point_names, attachment_points, audit_log, augmented_parts, auth, clamp_connections, imported_parts, iso_threads, manufacturers, part_colours, part_types, roles, scope_models, static_connections, sub_part_types, unc_threads, users from app.api import (
attachment_point_names,
attachment_points,
audit_log,
augmented_parts,
auth,
clamp_connections,
imported_parts,
iso_threads,
manufacturers,
part_colours,
part_types,
roles,
scope_models,
static_connections,
sub_part_types,
unc_threads,
users,
)
api_router = APIRouter() api_router = APIRouter()
api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(users.router, prefix="/users", tags=["users"])
api_router.include_router(roles.router, prefix="/roles", tags=["roles"]) api_router.include_router(roles.router, prefix="/roles", tags=["roles"])
api_router.include_router(iso_threads.router, prefix="/iso-threads", tags=["iso-threads"]) api_router.include_router(
api_router.include_router(unc_threads.router, prefix="/unc-threads", tags=["unc-threads"]) iso_threads.router, prefix="/iso-threads", tags=["iso-threads"]
api_router.include_router(manufacturers.router, prefix="/manufacturers", tags=["manufacturers"]) )
api_router.include_router(scope_models.router, prefix="/scope-models", tags=["scope-models"]) api_router.include_router(
api_router.include_router(part_colours.router, prefix="/part-colours", tags=["part-colours"]) unc_threads.router, prefix="/unc-threads", tags=["unc-threads"]
)
api_router.include_router(
manufacturers.router, prefix="/manufacturers", tags=["manufacturers"]
)
api_router.include_router(
scope_models.router, prefix="/scope-models", tags=["scope-models"]
)
api_router.include_router(
part_colours.router, prefix="/part-colours", tags=["part-colours"]
)
api_router.include_router(part_types.router, prefix="/part-types", tags=["part-types"]) api_router.include_router(part_types.router, prefix="/part-types", tags=["part-types"])
api_router.include_router(sub_part_types.router, prefix="/sub-part-types", tags=["sub-part-types"]) api_router.include_router(
api_router.include_router(static_connections.router, prefix="/static-connections", tags=["static-connections"]) sub_part_types.router, prefix="/sub-part-types", tags=["sub-part-types"]
api_router.include_router(clamp_connections.router, prefix="/clamp-connections", tags=["clamp-connections"]) )
api_router.include_router(attachment_point_names.router, prefix="/attachment-point-names", tags=["attachment-point-names"]) api_router.include_router(
api_router.include_router(attachment_points.router, prefix="/attachment-points", tags=["attachment-points"]) static_connections.router, prefix="/static-connections", tags=["static-connections"]
api_router.include_router(imported_parts.router, prefix="/imported-parts", tags=["imported-parts"]) )
api_router.include_router(augmented_parts.router, prefix="/augmented-parts", tags=["augmented-parts"]) api_router.include_router(
clamp_connections.router, prefix="/clamp-connections", tags=["clamp-connections"]
)
api_router.include_router(
attachment_point_names.router,
prefix="/attachment-point-names",
tags=["attachment-point-names"],
)
api_router.include_router(
attachment_points.router, prefix="/attachment-points", tags=["attachment-points"]
)
api_router.include_router(
imported_parts.router, prefix="/imported-parts", tags=["imported-parts"]
)
api_router.include_router(
augmented_parts.router, prefix="/augmented-parts", tags=["augmented-parts"]
)
api_router.include_router(audit_log.router, prefix="/audit-log", tags=["audit-log"]) api_router.include_router(audit_log.router, prefix="/audit-log", tags=["audit-log"])
+21 -6
View File
@@ -34,7 +34,10 @@ async def create_scope_model(
url=str(payload.url) if payload.url else None, url=str(payload.url) if payload.url else None,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="ScopeModel", db,
actor=actor,
action="create",
resource_type="ScopeModel",
resource_id=sm.id, resource_id=sm.id,
description=audit_crud.describe_create("ScopeModel", sm.id, sm.name), description=audit_crud.describe_create("ScopeModel", sm.id, sm.name),
) )
@@ -49,7 +52,9 @@ async def get_scope_model(
): ):
sm = await scope_model_crud.get(db, scope_model_id) sm = await scope_model_crud.get(db, scope_model_id)
if sm is None: if sm is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found"
)
return sm return sm
@@ -62,13 +67,18 @@ async def update_scope_model(
): ):
sm = await scope_model_crud.get(db, scope_model_id) sm = await scope_model_crud.get(db, scope_model_id)
if sm is None: if sm is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found"
)
data = payload.model_dump(exclude_unset=True) data = payload.model_dump(exclude_unset=True)
if "url" in data and data["url"] is not None: if "url" in data and data["url"] is not None:
data["url"] = str(data["url"]) data["url"] = str(data["url"])
sm = await scope_model_crud.update(db, sm, data) sm = await scope_model_crud.update(db, sm, data)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="ScopeModel", db,
actor=actor,
action="update",
resource_type="ScopeModel",
resource_id=sm.id, resource_id=sm.id,
description=audit_crud.describe_update("ScopeModel", sm.id, data), description=audit_crud.describe_update("ScopeModel", sm.id, data),
) )
@@ -83,11 +93,16 @@ async def delete_scope_model(
): ):
sm = await scope_model_crud.get(db, scope_model_id) sm = await scope_model_crud.get(db, scope_model_id)
if sm is None: if sm is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Scope model not found"
)
name = sm.name name = sm.name
await scope_model_crud.delete(db, sm) await scope_model_crud.delete(db, sm)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="ScopeModel", db,
actor=actor,
action="delete",
resource_type="ScopeModel",
resource_id=scope_model_id, resource_id=scope_model_id,
description=audit_crud.describe_delete("ScopeModel", scope_model_id, name), description=audit_crud.describe_delete("ScopeModel", scope_model_id, name),
) )
+21 -6
View File
@@ -25,17 +25,26 @@ async def list_static_connections(
return await static_connection_crud.list_all(db, skip=skip, limit=limit) return await static_connection_crud.list_all(db, skip=skip, limit=limit)
@router.post("", response_model=StaticConnectionRead, status_code=status.HTTP_201_CREATED) @router.post(
"", response_model=StaticConnectionRead, status_code=status.HTTP_201_CREATED
)
async def create_static_connection( async def create_static_connection(
payload: StaticConnectionCreate, payload: StaticConnectionCreate,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
actor: User = Depends(require_permission("static_connections:write")), actor: User = Depends(require_permission("static_connections:write")),
): ):
sc = await static_connection_crud.create(db, od_mm=payload.od_mm, od_in=payload.od_in) sc = await static_connection_crud.create(
db, od_mm=payload.od_mm, od_in=payload.od_in
)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="StaticConnection", db,
actor=actor,
action="create",
resource_type="StaticConnection",
resource_id=sc.id, resource_id=sc.id,
description=audit_crud.describe_create("StaticConnection", sc.id, f"{sc.od_mm} mm"), description=audit_crud.describe_create(
"StaticConnection", sc.id, f"{sc.od_mm} mm"
),
) )
return sc return sc
@@ -65,7 +74,10 @@ async def update_static_connection(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
sc = await static_connection_crud.update(db, sc, changes) sc = await static_connection_crud.update(db, sc, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="StaticConnection", db,
actor=actor,
action="update",
resource_type="StaticConnection",
resource_id=sc.id, resource_id=sc.id,
description=audit_crud.describe_update("StaticConnection", sc.id, changes), description=audit_crud.describe_update("StaticConnection", sc.id, changes),
) )
@@ -84,7 +96,10 @@ async def delete_static_connection(
od_mm = sc.od_mm od_mm = sc.od_mm
await static_connection_crud.delete(db, sc) await static_connection_crud.delete(db, sc)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="StaticConnection", db,
actor=actor,
action="delete",
resource_type="StaticConnection",
resource_id=static_connection_id, resource_id=static_connection_id,
description=audit_crud.describe_delete( description=audit_crud.describe_delete(
"StaticConnection", static_connection_id, f"{od_mm} mm" "StaticConnection", static_connection_id, f"{od_mm} mm"
+26 -7
View File
@@ -6,7 +6,11 @@ from app.crud import audit as audit_crud
from app.crud import sub_part_type as sub_part_type_crud from app.crud import sub_part_type as sub_part_type_crud
from app.deps import require_permission from app.deps import require_permission
from app.models.user import User from app.models.user import User
from app.schemas.sub_part_type import SubPartTypeCreate, SubPartTypeRead, SubPartTypeUpdate from app.schemas.sub_part_type import (
SubPartTypeCreate,
SubPartTypeRead,
SubPartTypeUpdate,
)
router = APIRouter() router = APIRouter()
@@ -31,7 +35,10 @@ async def create_sub_part_type(
db, name=payload.name, part_type_id=payload.part_type_id db, name=payload.name, part_type_id=payload.part_type_id
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="SubPartType", db,
actor=actor,
action="create",
resource_type="SubPartType",
resource_id=spt.id, resource_id=spt.id,
description=audit_crud.describe_create("SubPartType", spt.id, spt.name), description=audit_crud.describe_create("SubPartType", spt.id, spt.name),
) )
@@ -46,7 +53,9 @@ async def get_sub_part_type(
): ):
spt = await sub_part_type_crud.get(db, sub_part_type_id) spt = await sub_part_type_crud.get(db, sub_part_type_id)
if spt is None: if spt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found"
)
return spt return spt
@@ -59,11 +68,16 @@ async def update_sub_part_type(
): ):
spt = await sub_part_type_crud.get(db, sub_part_type_id) spt = await sub_part_type_crud.get(db, sub_part_type_id)
if spt is None: if spt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found"
)
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
spt = await sub_part_type_crud.update(db, spt, changes) spt = await sub_part_type_crud.update(db, spt, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="SubPartType", db,
actor=actor,
action="update",
resource_type="SubPartType",
resource_id=spt.id, resource_id=spt.id,
description=audit_crud.describe_update("SubPartType", spt.id, changes), description=audit_crud.describe_update("SubPartType", spt.id, changes),
) )
@@ -78,11 +92,16 @@ async def delete_sub_part_type(
): ):
spt = await sub_part_type_crud.get(db, sub_part_type_id) spt = await sub_part_type_crud.get(db, sub_part_type_id)
if spt is None: if spt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Sub part type not found"
)
name = spt.name name = spt.name
await sub_part_type_crud.delete(db, spt) await sub_part_type_crud.delete(db, spt)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="SubPartType", db,
actor=actor,
action="delete",
resource_type="SubPartType",
resource_id=sub_part_type_id, resource_id=sub_part_type_id,
description=audit_crud.describe_delete("SubPartType", sub_part_type_id, name), description=audit_crud.describe_delete("SubPartType", sub_part_type_id, name),
) )
+26 -7
View File
@@ -35,9 +35,16 @@ async def create_unc_thread(
depth_in=payload.depth_in, depth_in=payload.depth_in,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="UncThread", db,
actor=actor,
action="create",
resource_type="UncThread",
resource_id=thread.id, resource_id=thread.id,
description=audit_crud.describe_create("UncThread", thread.id, f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}"), description=audit_crud.describe_create(
"UncThread",
thread.id,
f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}",
),
) )
return thread return thread
@@ -50,7 +57,9 @@ async def get_unc_thread(
): ):
thread = await unc_thread_crud.get(db, unc_thread_id) thread = await unc_thread_crud.get(db, unc_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found"
)
return thread return thread
@@ -63,11 +72,16 @@ async def update_unc_thread(
): ):
thread = await unc_thread_crud.get(db, unc_thread_id) thread = await unc_thread_crud.get(db, unc_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found"
)
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
thread = await unc_thread_crud.update(db, thread, changes) thread = await unc_thread_crud.update(db, thread, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="UncThread", db,
actor=actor,
action="update",
resource_type="UncThread",
resource_id=thread.id, resource_id=thread.id,
description=audit_crud.describe_update("UncThread", thread.id, changes), description=audit_crud.describe_update("UncThread", thread.id, changes),
) )
@@ -82,11 +96,16 @@ async def delete_unc_thread(
): ):
thread = await unc_thread_crud.get(db, unc_thread_id) thread = await unc_thread_crud.get(db, unc_thread_id)
if thread is None: if thread is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found") raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="UNC thread not found"
)
name = f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}" name = f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}"
await unc_thread_crud.delete(db, thread) await unc_thread_crud.delete(db, thread)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="UncThread", db,
actor=actor,
action="delete",
resource_type="UncThread",
resource_id=unc_thread_id, resource_id=unc_thread_id,
description=audit_crud.describe_delete("UncThread", unc_thread_id, name), description=audit_crud.describe_delete("UncThread", unc_thread_id, name),
) )
+12 -3
View File
@@ -45,7 +45,10 @@ async def create_user(
role_ids=payload.role_ids, role_ids=payload.role_ids,
) )
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="create", resource_type="User", db,
actor=actor,
action="create",
resource_type="User",
resource_id=created.id, resource_id=created.id,
description=audit_crud.describe_create("User", created.id, created.email), description=audit_crud.describe_create("User", created.id, created.email),
) )
@@ -81,7 +84,10 @@ async def update_user(
changes = payload.model_dump(exclude_unset=True) changes = payload.model_dump(exclude_unset=True)
target = await user_crud.update(db, target, changes) target = await user_crud.update(db, target, changes)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="update", resource_type="User", db,
actor=actor,
action="update",
resource_type="User",
resource_id=user_id, resource_id=user_id,
description=audit_crud.describe_update("User", user_id, changes), description=audit_crud.describe_update("User", user_id, changes),
) )
@@ -102,7 +108,10 @@ async def delete_user(
email = target.email email = target.email
await user_crud.delete(db, target) await user_crud.delete(db, target)
await audit_crud.log( await audit_crud.log(
db, actor=actor, action="delete", resource_type="User", db,
actor=actor,
action="delete",
resource_type="User",
resource_id=user_id, resource_id=user_id,
description=audit_crud.describe_delete("User", user_id, email), description=audit_crud.describe_delete("User", user_id, email),
) )
+1 -3
View File
@@ -63,9 +63,7 @@ async def create(
return ap return ap
async def update( async def update(db: AsyncSession, ap: AttachmentPoint, data: dict) -> AttachmentPoint:
db: AsyncSession, ap: AttachmentPoint, data: dict
) -> AttachmentPoint:
for field, value in data.items(): for field, value in data.items():
setattr(ap, field, value) setattr(ap, field, value)
await db.commit() await db.commit()
+10 -9
View File
@@ -48,7 +48,9 @@ async def list_filtered(
q = q.where(AugmentedPart.part_type_id == part_type_id) q = q.where(AugmentedPart.part_type_id == part_type_id)
if sub_part_type_id is not None: if sub_part_type_id is not None:
q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id) q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id)
result = await db.execute(q.order_by(AugmentedPart.description).offset(skip).limit(limit)) result = await db.execute(
q.order_by(AugmentedPart.description).offset(skip).limit(limit)
)
return list(result.scalars().all()) return list(result.scalars().all())
@@ -62,6 +64,7 @@ async def count_filtered(
sub_part_type_id: int | None = None, sub_part_type_id: int | None = None,
) -> int: ) -> int:
from sqlalchemy import func from sqlalchemy import func
q = select(func.count()).select_from(AugmentedPart) q = select(func.count()).select_from(AugmentedPart)
if description.strip(): if description.strip():
q = q.where(AugmentedPart.description.ilike(f"%{description.strip()}%")) q = q.where(AugmentedPart.description.ilike(f"%{description.strip()}%"))
@@ -86,19 +89,18 @@ async def get_unlinked_imported_parts_filtered(
skip: int = 0, skip: int = 0,
limit: int = 50, limit: int = 50,
) -> list[ImportedPart]: ) -> list[ImportedPart]:
q = ( q = select(ImportedPart).where(
select(ImportedPart)
.where(
ImportedPart.InventoryID.not_in( ImportedPart.InventoryID.not_in(
select(augmented_part_imported_parts.c.imported_part_id) select(augmented_part_imported_parts.c.imported_part_id)
) )
) )
)
if description.strip(): if description.strip():
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
if colour.strip(): if colour.strip():
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%")) q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)) result = await db.execute(
q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)
)
return list(result.scalars().all()) return list(result.scalars().all())
@@ -108,6 +110,7 @@ async def count_unlinked_filtered(
colour: str = "", colour: str = "",
) -> int: ) -> int:
from sqlalchemy import func from sqlalchemy import func
q = ( q = (
select(func.count()) select(func.count())
.select_from(ImportedPart) .select_from(ImportedPart)
@@ -166,9 +169,7 @@ async def create(
return ap return ap
async def update( async def update(db: AsyncSession, ap: AugmentedPart, data: dict) -> AugmentedPart:
db: AsyncSession, ap: AugmentedPart, data: dict
) -> AugmentedPart:
for field, value in data.items(): for field, value in data.items():
setattr(ap, field, value) setattr(ap, field, value)
await db.commit() await db.commit()
+4 -4
View File
@@ -13,7 +13,9 @@ async def list_all(
) -> list[ClampConnection]: ) -> list[ClampConnection]:
result = await db.execute( result = await db.execute(
select(ClampConnection) select(ClampConnection)
.order_by(ClampConnection.min_mm.nulls_last(), ClampConnection.min_in.nulls_last()) .order_by(
ClampConnection.min_mm.nulls_last(), ClampConnection.min_in.nulls_last()
)
.offset(skip) .offset(skip)
.limit(limit) .limit(limit)
) )
@@ -35,9 +37,7 @@ async def create(
return cc return cc
async def update( async def update(db: AsyncSession, cc: ClampConnection, data: dict) -> ClampConnection:
db: AsyncSession, cc: ClampConnection, data: dict
) -> ClampConnection:
for field, value in data.items(): for field, value in data.items():
setattr(cc, field, value) setattr(cc, field, value)
await db.commit() await db.commit()
+24 -8
View File
@@ -3,11 +3,18 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.imported_part import ImportedPart from app.models.imported_part import ImportedPart
_FIELDS = frozenset({ _FIELDS = frozenset(
"InventoryID", "Description", "TBXColour", {
"ProductMaxThreadDepthmm", "ProductMaxThreadDepthin", "InventoryID",
"ProductODmm", "ProductODin", "TBXThreadAttribute", "Description",
}) "TBXColour",
"ProductMaxThreadDepthmm",
"ProductMaxThreadDepthin",
"ProductODmm",
"ProductODin",
"TBXThreadAttribute",
}
)
async def get(db: AsyncSession, inventory_id: str) -> ImportedPart | None: async def get(db: AsyncSession, inventory_id: str) -> ImportedPart | None:
@@ -18,7 +25,10 @@ async def list_all(
db: AsyncSession, skip: int = 0, limit: int = 500 db: AsyncSession, skip: int = 0, limit: int = 500
) -> list[ImportedPart]: ) -> list[ImportedPart]:
result = await db.execute( result = await db.execute(
select(ImportedPart).order_by(ImportedPart.InventoryID).offset(skip).limit(limit) select(ImportedPart)
.order_by(ImportedPart.InventoryID)
.offset(skip)
.limit(limit)
) )
return list(result.scalars().all()) return list(result.scalars().all())
@@ -32,7 +42,9 @@ async def list_filtered(
q = select(ImportedPart) q = select(ImportedPart)
if description.strip(): if description.strip():
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)) result = await db.execute(
q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit)
)
return list(result.scalars().all()) return list(result.scalars().all())
@@ -47,12 +59,14 @@ async def distinct_colours(db: AsyncSession) -> list[str]:
async def count(db: AsyncSession) -> int: async def count(db: AsyncSession) -> int:
from sqlalchemy import func from sqlalchemy import func
result = await db.execute(select(func.count()).select_from(ImportedPart)) result = await db.execute(select(func.count()).select_from(ImportedPart))
return result.scalar_one() return result.scalar_one()
async def count_filtered(db: AsyncSession, description: str = "") -> int: async def count_filtered(db: AsyncSession, description: str = "") -> int:
from sqlalchemy import func from sqlalchemy import func
q = select(func.count()).select_from(ImportedPart) q = select(func.count()).select_from(ImportedPart)
if description.strip(): if description.strip():
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%")) q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
@@ -81,7 +95,9 @@ async def upsert_bulk(db: AsyncSession, records: list[dict]) -> tuple[int, int]:
incoming_ids = {r["InventoryID"] for r in clean} incoming_ids = {r["InventoryID"] for r in clean}
result = await db.execute( result = await db.execute(
select(ImportedPart.InventoryID).where(ImportedPart.InventoryID.in_(incoming_ids)) select(ImportedPart.InventoryID).where(
ImportedPart.InventoryID.in_(incoming_ids)
)
) )
existing_ids = {row[0] for row in result.all()} existing_ids = {row[0] for row in result.all()}
+6 -2
View File
@@ -16,7 +16,9 @@ async def get_by_values(
depth_mm: float | None, depth_mm: float | None,
) -> IsoThread | None: ) -> IsoThread | None:
depth_clause = ( depth_clause = (
IsoThread.depth_mm.is_(None) if depth_mm is None else IsoThread.depth_mm == depth_mm IsoThread.depth_mm.is_(None)
if depth_mm is None
else IsoThread.depth_mm == depth_mm
) )
result = await db.execute( result = await db.execute(
select(IsoThread).where( select(IsoThread).where(
@@ -28,7 +30,9 @@ async def get_by_values(
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[IsoThread]: async def list_all(
db: AsyncSession, skip: int = 0, limit: int = 100
) -> list[IsoThread]:
result = await db.execute( result = await db.execute(
select(IsoThread).order_by(IsoThread.id).offset(skip).limit(limit) select(IsoThread).order_by(IsoThread.id).offset(skip).limit(limit)
) )
+1 -3
View File
@@ -9,9 +9,7 @@ async def get(db: AsyncSession, manufacturer_id: int) -> Manufacturer | None:
async def get_by_name(db: AsyncSession, name: str) -> Manufacturer | None: async def get_by_name(db: AsyncSession, name: str) -> Manufacturer | None:
result = await db.execute( result = await db.execute(select(Manufacturer).where(Manufacturer.name == name))
select(Manufacturer).where(Manufacturer.name == name)
)
return result.scalar_one_or_none() return result.scalar_one_or_none()
+1 -3
View File
@@ -9,9 +9,7 @@ async def get(db: AsyncSession, part_colour_id: int) -> PartColour | None:
async def get_by_name(db: AsyncSession, name: str) -> PartColour | None: async def get_by_name(db: AsyncSession, name: str) -> PartColour | None:
result = await db.execute( result = await db.execute(select(PartColour).where(PartColour.name == name))
select(PartColour).where(PartColour.name == name)
)
return result.scalar_one_or_none() return result.scalar_one_or_none()
+1 -3
View File
@@ -13,9 +13,7 @@ async def get_by_name(db: AsyncSession, name: str) -> PartType | None:
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def list_all( async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[PartType]:
db: AsyncSession, skip: int = 0, limit: int = 100
) -> list[PartType]:
result = await db.execute( result = await db.execute(
select(PartType).order_by(PartType.name).offset(skip).limit(limit) select(PartType).order_by(PartType.name).offset(skip).limit(limit)
) )
+4 -1
View File
@@ -19,7 +19,10 @@ async def list_all(
db: AsyncSession, skip: int = 0, limit: int = 100 db: AsyncSession, skip: int = 0, limit: int = 100
) -> list[StaticConnection]: ) -> list[StaticConnection]:
result = await db.execute( result = await db.execute(
select(StaticConnection).order_by(StaticConnection.od_mm).offset(skip).limit(limit) select(StaticConnection)
.order_by(StaticConnection.od_mm)
.offset(skip)
.limit(limit)
) )
return list(result.scalars().all()) return list(result.scalars().all())
+1 -3
View File
@@ -28,9 +28,7 @@ async def list_all(
return list(result.scalars().all()) return list(result.scalars().all())
async def create( async def create(db: AsyncSession, *, name: str, part_type_id: int) -> SubPartType:
db: AsyncSession, *, name: str, part_type_id: int
) -> SubPartType:
spt = SubPartType(name=name, part_type_id=part_type_id) spt = SubPartType(name=name, part_type_id=part_type_id)
db.add(spt) db.add(spt)
await db.commit() await db.commit()
+6 -2
View File
@@ -17,7 +17,9 @@ async def get_by_values(
depth_in: float | None, depth_in: float | None,
) -> UncThread | None: ) -> UncThread | None:
depth_clause = ( depth_clause = (
UncThread.depth_in.is_(None) if depth_in is None else UncThread.depth_in == depth_in UncThread.depth_in.is_(None)
if depth_in is None
else UncThread.depth_in == depth_in
) )
result = await db.execute( result = await db.execute(
select(UncThread).where( select(UncThread).where(
@@ -30,7 +32,9 @@ async def get_by_values(
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[UncThread]: async def list_all(
db: AsyncSession, skip: int = 0, limit: int = 100
) -> list[UncThread]:
result = await db.execute( result = await db.execute(
select(UncThread).order_by(UncThread.id).offset(skip).limit(limit) select(UncThread).order_by(UncThread.id).offset(skip).limit(limit)
) )
+5 -1
View File
@@ -1,6 +1,10 @@
"""Import all models so they register with ``Base.metadata``.""" """Import all models so they register with ``Base.metadata``."""
from app.models.associations import augmented_part_imported_parts, role_permissions, user_roles from app.models.associations import (
augmented_part_imported_parts,
role_permissions,
user_roles,
)
from app.models.attachment_point import AttachmentPoint from app.models.attachment_point import AttachmentPoint
from app.models.attachment_point_name import AttachmentPointName from app.models.attachment_point_name import AttachmentPointName
from app.models.audit_log import AuditLog from app.models.audit_log import AuditLog
+6 -2
View File
@@ -29,10 +29,14 @@ class AttachmentPoint(Base):
ForeignKey("unc_threads.id", ondelete="RESTRICT"), nullable=True, default=None ForeignKey("unc_threads.id", ondelete="RESTRICT"), nullable=True, default=None
) )
clamp_connection_id: Mapped[int | None] = mapped_column( clamp_connection_id: Mapped[int | None] = mapped_column(
ForeignKey("clamp_connections.id", ondelete="RESTRICT"), nullable=True, default=None ForeignKey("clamp_connections.id", ondelete="RESTRICT"),
nullable=True,
default=None,
) )
static_connection_id: Mapped[int | None] = mapped_column( static_connection_id: Mapped[int | None] = mapped_column(
ForeignKey("static_connections.id", ondelete="RESTRICT"), nullable=True, default=None ForeignKey("static_connections.id", ondelete="RESTRICT"),
nullable=True,
default=None,
) )
attachment_point_name: Mapped[AttachmentPointName] = relationship(lazy="selectin") attachment_point_name: Mapped[AttachmentPointName] = relationship(lazy="selectin")
+3 -1
View File
@@ -30,7 +30,9 @@ class AugmentedPart(Base):
ForeignKey("part_types.id", ondelete="RESTRICT"), nullable=False ForeignKey("part_types.id", ondelete="RESTRICT"), nullable=False
) )
sub_part_type_id: Mapped[int | None] = mapped_column( sub_part_type_id: Mapped[int | None] = mapped_column(
ForeignKey("sub_part_types.id", ondelete="SET NULL"), nullable=True, default=None ForeignKey("sub_part_types.id", ondelete="SET NULL"),
nullable=True,
default=None,
) )
# linked imported parts — not eagerly loaded; used by sync service only # linked imported parts — not eagerly loaded; used by sync service only
+6 -2
View File
@@ -10,8 +10,12 @@ class ImportedPart(Base):
InventoryID: Mapped[str] = mapped_column(String, primary_key=True) InventoryID: Mapped[str] = mapped_column(String, primary_key=True)
Description: Mapped[str | None] = mapped_column(nullable=True, default=None) Description: Mapped[str | None] = mapped_column(nullable=True, default=None)
TBXColour: Mapped[str | None] = mapped_column(nullable=True, default=None) TBXColour: Mapped[str | None] = mapped_column(nullable=True, default=None)
ProductMaxThreadDepthmm: Mapped[str | None] = mapped_column(nullable=True, default=None) ProductMaxThreadDepthmm: Mapped[str | None] = mapped_column(
ProductMaxThreadDepthin: Mapped[str | None] = mapped_column(nullable=True, default=None) nullable=True, default=None
)
ProductMaxThreadDepthin: Mapped[str | None] = mapped_column(
nullable=True, default=None
)
ProductODmm: Mapped[str | None] = mapped_column(nullable=True, default=None) ProductODmm: Mapped[str | None] = mapped_column(nullable=True, default=None)
ProductODin: Mapped[str | None] = mapped_column(nullable=True, default=None) ProductODin: Mapped[str | None] = mapped_column(nullable=True, default=None)
TBXThreadAttribute: Mapped[str | None] = mapped_column(nullable=True, default=None) TBXThreadAttribute: Mapped[str | None] = mapped_column(nullable=True, default=None)
+26 -9
View File
@@ -87,7 +87,6 @@ def _parse_thread_attribute(value: str) -> tuple[str, dict] | None:
return "iso", { return "iso", {
"diameter_mm": float(m.group("diameter")), "diameter_mm": float(m.group("diameter")),
"pitch_mm": float(m.group("pitch")), "pitch_mm": float(m.group("pitch")),
"extra": float(m.group("extra")),
} }
if m := _UNC_THREAD_RE.match(value): if m := _UNC_THREAD_RE.match(value):
@@ -110,7 +109,9 @@ async def _get_or_create_attachment_point_name(db: AsyncSession, name: str):
return apn return apn
async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: ImportedPart) -> None: async def _add_attachment_point(
db: AsyncSession, ap: AugmentedPart, ip: ImportedPart
) -> None:
"""Infer and create an attachment point for a newly-synced AugmentedPart. """Infer and create an attachment point for a newly-synced AugmentedPart.
Preference order: TBXThreadAttribute (ISO/UNC thread) then ProductODmm/in Preference order: TBXThreadAttribute (ISO/UNC thread) then ProductODmm/in
@@ -126,7 +127,9 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
if kind == "iso": if kind == "iso":
depth_mm = _parse_float(ip.ProductMaxThreadDepthmm) depth_mm = _parse_float(ip.ProductMaxThreadDepthmm)
thread = await iso_thread_crud.get_by_values(db, depth_mm=depth_mm, **fields) thread = await iso_thread_crud.get_by_values(
db, depth_mm=depth_mm, **fields
)
if thread is None: if thread is None:
thread = await iso_thread_crud.create(db, depth_mm=depth_mm, **fields) thread = await iso_thread_crud.create(db, depth_mm=depth_mm, **fields)
await attachment_point_crud.create( await attachment_point_crud.create(
@@ -137,7 +140,9 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
) )
elif kind == "unc": elif kind == "unc":
depth_in = _parse_float(ip.ProductMaxThreadDepthin) depth_in = _parse_float(ip.ProductMaxThreadDepthin)
thread = await unc_thread_crud.get_by_values(db, depth_in=depth_in, **fields) thread = await unc_thread_crud.get_by_values(
db, depth_in=depth_in, **fields
)
if thread is None: if thread is None:
thread = await unc_thread_crud.create(db, depth_in=depth_in, **fields) thread = await unc_thread_crud.create(db, depth_in=depth_in, **fields)
await attachment_point_crud.create( await attachment_point_crud.create(
@@ -146,6 +151,8 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
attachment_point_name_id=apn.id, attachment_point_name_id=apn.id,
unc_thread_id=thread.id, unc_thread_id=thread.id,
) )
else:
pass
return return
od_mm = _parse_float(ip.ProductODmm) od_mm = _parse_float(ip.ProductODmm)
@@ -233,7 +240,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# ------------------------------------------------------------------ phase 1 # ------------------------------------------------------------------ phase 1
# IDs referenced in existing associations # IDs referenced in existing associations
result = await db.execute(select(augmented_part_imported_parts.c.imported_part_id).distinct()) result = await db.execute(
select(augmented_part_imported_parts.c.imported_part_id).distinct()
)
all_linked_ids: set[str] = {row[0] for row in result.all()} all_linked_ids: set[str] = {row[0] for row in result.all()}
# IDs that still exist in imported_parts # IDs that still exist in imported_parts
@@ -247,7 +256,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# Which AugmentedParts are affected? # Which AugmentedParts are affected?
result = await db.execute( result = await db.execute(
select(augmented_part_imported_parts.c.augmented_part_id).where( select(augmented_part_imported_parts.c.augmented_part_id).where(
augmented_part_imported_parts.c.imported_part_id.in_(orphaned_import_ids) augmented_part_imported_parts.c.imported_part_id.in_(
orphaned_import_ids
)
) )
) )
orphaned_ap_ids: set[int] = {row[0] for row in result.all()} orphaned_ap_ids: set[int] = {row[0] for row in result.all()}
@@ -255,7 +266,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# Remove the stale association rows # Remove the stale association rows
await db.execute( await db.execute(
augmented_part_imported_parts.delete().where( augmented_part_imported_parts.delete().where(
augmented_part_imported_parts.c.imported_part_id.in_(orphaned_import_ids) augmented_part_imported_parts.c.imported_part_id.in_(
orphaned_import_ids
)
) )
) )
await db.flush() await db.flush()
@@ -278,7 +291,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# ------------------------------------------------------------------ phase 2 # ------------------------------------------------------------------ phase 2
# Re-query linked IDs after phase 1 cleanup # Re-query linked IDs after phase 1 cleanup
result = await db.execute(select(augmented_part_imported_parts.c.imported_part_id).distinct()) result = await db.execute(
select(augmented_part_imported_parts.c.imported_part_id).distinct()
)
now_linked_ids: set[str] = {row[0] for row in result.all()} now_linked_ids: set[str] = {row[0] for row in result.all()}
unlinked_ids = current_ids - now_linked_ids unlinked_ids = current_ids - now_linked_ids
@@ -286,7 +301,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
skipped = 0 skipped = 0
if not unlinked_ids: if not unlinked_ids:
return AugmentedPartSyncResult(created=created, deleted=deleted, skipped=skipped) return AugmentedPartSyncResult(
created=created, deleted=deleted, skipped=skipped
)
result = await db.execute( result = await db.execute(
select(ImportedPart).where(ImportedPart.InventoryID.in_(unlinked_ids)) select(ImportedPart).where(ImportedPart.InventoryID.in_(unlinked_ids))
+1 -1
View File
@@ -12,7 +12,7 @@ async def fetch(
records: list[dict] = [] records: list[dict] = []
next_url: str | None = url next_url: str | None = url
headers= {"Accept": "application/json" } headers = {"Accept": "application/json"}
async with httpx.AsyncClient(timeout=30.0) as client: async with httpx.AsyncClient(timeout=30.0) as client:
while next_url: while next_url:
response = await client.get(next_url, auth=auth, headers=headers) response = await client.get(next_url, auth=auth, headers=headers)
+425 -119
View File
File diff suppressed because it is too large. Load diff
+25 -3
View File
@@ -77,7 +77,22 @@ PERMISSIONS = [
# Default roles -> permission names. "admin" gets everything. # Default roles -> permission names. "admin" gets everything.
ROLES = { ROLES = {
"admin": [name for name, _ in PERMISSIONS], # includes audit_log:read "admin": [name for name, _ in PERMISSIONS], # includes audit_log:read
"user": ["users:read", "iso_threads:read", "unc_threads:read", "manufacturers:read", "scope_models:read", "part_colours:read", "part_types:read", "sub_part_types:read", "imported_parts:read", "static_connections:read", "clamp_connections:read", "attachment_point_names:read", "attachment_points:read", "augmented_parts:read"], "user": [
"users:read",
"iso_threads:read",
"unc_threads:read",
"manufacturers:read",
"scope_models:read",
"part_colours:read",
"part_types:read",
"sub_part_types:read",
"imported_parts:read",
"static_connections:read",
"clamp_connections:read",
"attachment_point_names:read",
"attachment_points:read",
"augmented_parts:read",
],
} }
@@ -123,8 +138,15 @@ async def seed() -> None:
pt = await part_type_crud.get_by_name(db, pt_name) pt = await part_type_crud.get_by_name(db, pt_name)
if pt is not None: if pt is not None:
for spt_name in spt_names: for spt_name in spt_names:
if await sub_part_type_crud.get_by_name_and_part_type(db, spt_name, pt.id) is None: if (
await sub_part_type_crud.create(db, name=spt_name, part_type_id=pt.id) await sub_part_type_crud.get_by_name_and_part_type(
db, spt_name, pt.id
)
is None
):
await sub_part_type_crud.create(
db, name=spt_name, part_type_id=pt.id
)
# Superuser # Superuser
existing = await user_crud.get_by_email(db, settings.first_superuser_email) existing = await user_crud.get_by_email(db, settings.first_superuser_email)