Coverage for src\airtable_to_sqlite\schema.py: 100%

76 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-05 19:54 +0100

1import logging 

2from copy import copy 

3from dataclasses import dataclass 

4from typing import Any, Dict, Generator, List, Optional 

5 

6from pyairtable.api.base import Base as AirtableBase 

7 

8from airtable_to_sqlite.constants import ( 

9 NUMBER_FIELD_TYPES, 

10 OPTION_FIELDS, 

11 PreferedNamingMethod, 

12) 

13 

14logger = logging.getLogger(__name__) 

15 

16 

17@dataclass 

18class BaseRecord: 

19 id: str # noqa: A003 

20 name: str 

21 permissionLevel: str # noqa: N815 

22 

23 

24@dataclass 

25class FieldSchema: 

26 id: str # noqa: A003 

27 name: str 

28 type: str # noqa: A003 

29 options: Optional[Dict[str, Any]] = None 

30 

31 def db_name(self, prefers_ids: PreferedNamingMethod = PreferedNamingMethod.NAME) -> str: 

32 if prefers_ids == PreferedNamingMethod.ID: 

33 return self.id 

34 return self.name 

35 

36 @property 

37 def column_type(self) -> Any: 

38 field_type = self.type 

39 if field_type == "formula": 

40 if self.options is not None: 

41 field_type = self.options.get("result", {}).get("type", field_type) 

42 

43 if field_type == "multipleRecordLinks": 

44 return None 

45 if field_type in NUMBER_FIELD_TYPES: 

46 return float 

47 if field_type in ("checkbox"): 

48 return bool 

49 return str 

50 

51 @property 

52 def choices(self) -> Optional[List[Dict[str, Any]]]: 

53 options = self.options 

54 if options is None: 

55 return None 

56 

57 return [ 

58 { 

59 "id": choice.get("id"), 

60 "name": choice.get("name"), 

61 "color": choice.get("color"), 

62 "fieldId": self.id, 

63 } 

64 for choice in options.get("choices", []) 

65 ] 

66 

67 def for_insertion(self, table: "TableSchema") -> Dict[str, Any]: 

68 options = copy(self.options) 

69 field_to_insert: Dict[str, Any] = { 

70 "id": self.id, 

71 "name": self.name, 

72 "type": self.type, 

73 "tableId": table.id, 

74 } 

75 if options is None: 

76 for option_field in OPTION_FIELDS.keys(): 

77 field_to_insert[option_field] = None 

78 else: 

79 for option_field in OPTION_FIELDS.keys(): 

80 field_to_insert[option_field] = options.pop(option_field, None) 

81 field_to_insert["options"] = options 

82 return field_to_insert 

83 

84 

85@dataclass 

86class ViewSchema: 

87 id: str # noqa: A003 

88 name: str 

89 type: str # noqa: A003 

90 

91 def db_name(self, prefers_ids: PreferedNamingMethod = PreferedNamingMethod.NAME) -> str: 

92 if prefers_ids == PreferedNamingMethod.ID: 

93 return self.id 

94 return self.name 

95 

96 

97@dataclass 

98class TableSchema: 

99 id: str # noqa: A003 

100 name: str 

101 primaryFieldId: str # noqa: N815 

102 fields: List[FieldSchema] 

103 views: List[ViewSchema] 

104 

105 def db_name(self, prefers_ids: PreferedNamingMethod = PreferedNamingMethod.NAME) -> str: 

106 if prefers_ids == PreferedNamingMethod.ID: 

107 return self.id 

108 return self.name 

109 

110 def get_table_data(self, api: AirtableBase) -> Generator[Dict[str, Any], None, None]: 

111 logger.info(f"Fetching table data for {self.name} from Airtable...") 

112 for page in api.iterate(self.name): 

113 for record in page: 

114 yield record