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

1""" 

2Attributes related APIs. 

3""" 

4 

5import logging 

6from typing import List 

7 

8from fastapi import APIRouter, Depends 

9from sqlmodel import Session, select 

10 

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 

19 

20_logger = logging.getLogger(__name__) 

21router = APIRouter() 

22 

23 

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() 

30 

31 

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 

54 

55 

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} 

109 

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_) 

124 

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()