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

50 statements  

« prev     ^ index     » next       coverage.py v7.4.2, created at 2024-02-22 10:56 +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 __future__ import annotations 

23 

24from collections.abc import Iterable, Mapping, MutableMapping 

25from typing import Any 

26 

27_Mapping = Mapping[str, Any] 

28_MutableMapping = MutableMapping[str, Any] 

29 

30 

31class ReorderingVisitor: 

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

33 

34 Parameters 

35 ---------- 

36 add_type : `bool` 

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

38 """ 

39 

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

41 self.add_type = add_type 

42 

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

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

45 # Override with default 

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

47 schema_obj["tables"] = tables 

48 if self.add_type: 

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

50 return _new_order( 

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

52 ) 

53 

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

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

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

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

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

59 table_obj["columns"] = columns 

60 if primary_key: 

61 table_obj["primaryKey"] = primary_key 

62 if constraints: 

63 table_obj["constraints"] = constraints 

64 if indexes: 

65 table_obj["indexes"] = indexes 

66 if self.add_type: 

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

68 return _new_order( 

69 table_obj, 

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

71 ) 

72 

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

74 if self.add_type: 

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

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

77 

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

79 # FIXME: Handle Primary Keys 

80 return primary_key_obj 

81 

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

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

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

85 

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

87 if self.add_type: 

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

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

90 

91 

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

93 reordered_object: _MutableMapping = {} 

94 for name in order: 

95 if name in obj: 

96 reordered_object[name] = obj[name] 

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

98 if key not in reordered_object: 

99 reordered_object[key] = value 

100 return reordered_object