Coverage for python/felis/utils.py: 16%

49 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-23 10:44 +0000

1# This file is part of felis. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22from collections.abc import Iterable, Mapping, MutableMapping 

23from typing import Any 

24 

25_Mapping = Mapping[str, Any] 

26_MutableMapping = MutableMapping[str, Any] 

27 

28 

29class ReorderingVisitor: 

30 """A visitor that reorders and optionally adds the "@type". 

31 

32 Parameters 

33 ---------- 

34 add_type : `bool` 

35 If true, add the "@type" if it doesn't exist. 

36 """ 

37 

38 def __init__(self, add_type: bool = False): 

39 self.add_type = add_type 

40 

41 def visit_schema(self, schema_obj: _MutableMapping) -> _Mapping: 

42 """Process schema, the input MUST be a normalized representation.""" 

43 # Override with default 

44 tables = [self.visit_table(table_obj, schema_obj) for table_obj in schema_obj["tables"]] 

45 schema_obj["tables"] = tables 

46 if self.add_type: 

47 schema_obj["@type"] = schema_obj.get("@type", "Schema") 

48 return _new_order( 

49 schema_obj, ["@context", "name", "@id", "@type", "description", "tables", "version"] 

50 ) 

51 

52 def visit_table(self, table_obj: _MutableMapping, schema_obj: _Mapping) -> _Mapping: 

53 columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]] 

54 primary_key = self.visit_primary_key(table_obj.get("primaryKey", []), table_obj) 

55 constraints = [self.visit_constraint(c, table_obj) for c in table_obj.get("constraints", [])] 

56 indexes = [self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])] 

57 table_obj["columns"] = columns 

58 if primary_key: 

59 table_obj["primaryKey"] = primary_key 

60 if constraints: 

61 table_obj["constraints"] = constraints 

62 if indexes: 

63 table_obj["indexes"] = indexes 

64 if self.add_type: 

65 table_obj["@type"] = table_obj.get("@type", "Table") 

66 return _new_order( 

67 table_obj, 

68 ["name", "@id", "@type", "description", "columns", "primaryKey", "constraints", "indexes"], 

69 ) 

70 

71 def visit_column(self, column_obj: _MutableMapping, table_obj: _Mapping) -> _Mapping: 

72 if self.add_type: 

73 column_obj["@type"] = column_obj.get("@type", "Column") 

74 return _new_order(column_obj, ["name", "@id", "@type", "description", "datatype"]) 

75 

76 def visit_primary_key(self, primary_key_obj: _MutableMapping, table: _Mapping) -> _Mapping: 

77 # FIXME: Handle Primary Keys 

78 return primary_key_obj 

79 

80 def visit_constraint(self, constraint_obj: _MutableMapping, table: _Mapping) -> _Mapping: 

81 # Type MUST be present... we can skip 

82 return _new_order(constraint_obj, ["name", "@id", "@type", "description"]) 

83 

84 def visit_index(self, index_obj: _MutableMapping, table: _Mapping) -> _Mapping: 

85 if self.add_type: 

86 index_obj["@type"] = index_obj.get("@type", "Index") 

87 return _new_order(index_obj, ["name", "@id", "@type", "description"]) 

88 

89 

90def _new_order(obj: _Mapping, order: Iterable[str]) -> _Mapping: 

91 reordered_object: _MutableMapping = {} 

92 for name in order: 

93 if name in obj: 

94 reordered_object[name] = obj[name] 

95 for key, value in obj.items(): 

96 if key not in reordered_object: 

97 reordered_object[key] = value 

98 return reordered_object