Coverage for dj/api/attributes.py: 100%
39 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-17 20:05 -0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-17 20:05 -0700
1"""
2Attributes related APIs.
3"""
5import logging
6from typing import List
8from fastapi import APIRouter, Depends
9from sqlmodel import Session, select
11from dj.errors import DJAlreadyExistsException, DJException
12from dj.models.attribute import (
13 RESERVED_ATTRIBUTE_NAMESPACE,
14 AttributeType,
15 MutableAttributeTypeFields,
16)
17from dj.models.node import NodeType
18from dj.utils import get_session
20_logger = logging.getLogger(__name__)
21router = APIRouter()
24@router.get("/attributes/", response_model=List[AttributeType])
25def list_attributes(*, session: Session = Depends(get_session)) -> List[AttributeType]:
26 """
27 List all available attribute types.
28 """
29 return session.exec(select(AttributeType)).all()
32@router.post("/attributes/", response_model=AttributeType, status_code=201)
33def add_an_attribute_type(
34 data: MutableAttributeTypeFields, *, session: Session = Depends(get_session)
35) -> AttributeType:
36 """
37 Add a new attribute type
38 """
39 if data.namespace == RESERVED_ATTRIBUTE_NAMESPACE:
40 raise DJException(
41 message="Cannot use `system` as the attribute type namespace as it is reserved.",
42 )
43 statement = select(AttributeType).where(AttributeType.name == data.name)
44 attribute_type = session.exec(statement).unique().one_or_none()
45 if attribute_type:
46 raise DJAlreadyExistsException(
47 message=f"Attribute type `{data.name}` already exists!",
48 )
49 attribute_type = AttributeType.from_orm(data)
50 session.add(attribute_type)
51 session.commit()
52 session.refresh(attribute_type)
53 return attribute_type
56def default_attribute_types(session: Session = Depends(get_session)):
57 """
58 Loads all the column attribute types that are supported by the system
59 by default into the database.
60 """
61 defaults = [
62 AttributeType(
63 namespace=RESERVED_ATTRIBUTE_NAMESPACE,
64 name="primary_key",
65 description="Points to a column which is part of the primary key of the node",
66 uniqueness_scope=[],
67 allowed_node_types=[
68 NodeType.SOURCE,
69 NodeType.TRANSFORM,
70 NodeType.DIMENSION,
71 ],
72 ),
73 AttributeType(
74 namespace=RESERVED_ATTRIBUTE_NAMESPACE,
75 name="event_time",
76 description="Points to a column which represents the time of the event in a given "
77 "fact related node. Used to facilitate proper joins with dimension node "
78 "to match the desired effect.",
79 uniqueness_scope=["node", "column_type"],
80 allowed_node_types=[NodeType.SOURCE, NodeType.TRANSFORM],
81 ),
82 AttributeType(
83 namespace=RESERVED_ATTRIBUTE_NAMESPACE,
84 name="effective_time",
85 description="Points to a column which represents the effective time of a row in a "
86 "dimension node. Used to facilitate proper joins with fact nodes"
87 " on event time.",
88 uniqueness_scope=["node", "column_type"],
89 allowed_node_types=[NodeType.DIMENSION],
90 ),
91 AttributeType(
92 namespace=RESERVED_ATTRIBUTE_NAMESPACE,
93 name="expired_time",
94 description="Points to a column which represents the expired time of a row in a "
95 "dimension node. Used to facilitate proper joins with fact nodes "
96 "on event time.",
97 uniqueness_scope=["node", "column_type"],
98 allowed_node_types=[NodeType.DIMENSION],
99 ),
100 AttributeType(
101 namespace=RESERVED_ATTRIBUTE_NAMESPACE,
102 name="dimension",
103 description="Points to a dimension attribute column",
104 uniqueness_scope=[],
105 allowed_node_types=[NodeType.SOURCE, NodeType.TRANSFORM],
106 ),
107 ]
108 default_attribute_type_names = {type_.name: type_ for type_ in defaults}
110 # Update existing default attribute types
111 statement = select(AttributeType).filter(
112 # pylint: disable=no-member
113 AttributeType.name.in_( # type: ignore
114 set(default_attribute_type_names.keys()),
115 ),
116 )
117 attribute_types = session.exec(statement).all()
118 for type_ in attribute_types:
119 updated_type = default_attribute_type_names[type_.name].dict(
120 exclude_unset=True,
121 )
122 type_ = type_.update(updated_type)
123 session.add(type_)
125 # Add new default attribute types
126 new_types = set(default_attribute_type_names.keys()) - {
127 type_.name for type_ in attribute_types
128 }
129 for name in new_types:
130 session.add(default_attribute_type_names[name])
131 session.commit()