'''
Copyright 2024 European Commission
*
Licensed under the EUPL, Version 1.2;
You may not use this work except in compliance with the Licence.
You may obtain a copy of the Licence at:
*
https://joinup.ec.europa.eu/software/page/eupl
*
Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the Licence for the specific language governing permissions and limitations under the Licence.
'''
import re
import pandas as pd
[docs]
class Container:
"""
This class is a container for objects that allow for indexing and representation in multiple ways:
via keys that are the name of the objects
or via integer indexing as in a list.
"""
def __init__(self,idDict=False):
self.containerDict = {}
self.containerList = []
self.idDict = idDict
if idDict:
self.dictIds = {}
def add(self,key,value,identifier=None):
self.containerDict[key] = value
self.containerList.append(value)
if self.idDict:
self.dictIds[identifier] = value
def _short_repr(self):
if len(self) > 10:
return f"{len(self)} elements"
elif len(self) == 0:
return "0 elements"
else:
rep = ""
for i,el in enumerate(self):
if hasattr(el,"_short_repr"):
rep += f"{el._short_repr()}"
elif isinstance(el,pd.DataFrame):
rep += f"Pandas DataFrame of {len(el.columns)} variables and {len(el)} observations."
else:
rep += f"{el.__class__.__name__}"
if i < len(self)-1:
rep += ", "
return rep
def _get_by_id(self,id):
if not self.idDict:
raise Exception("idDict not generated for this type")
else:
return self.dictIds[id]
def __repr__(self):
s= ""
maxlen_begin = 0
maxlen_middle = 0
end_is_empty = True
for i,el in enumerate(self.containerList):
maxlen_begin = maxlen_begin if len(el._container_begin_repr()) + len(str(i)) < maxlen_begin else len(el._container_begin_repr()) + len(str(i))
repr_middle = el._container_middle_repr()
maxlen_middle = maxlen_middle if len(repr_middle) + len(str(i)) < maxlen_middle else len(repr_middle) + len(str(i))
if len(el._container_end_repr()) > 0:
end_is_empty = False
for i,el in enumerate(self.containerList):
name_repr = el._container_begin_repr() + " "*(maxlen_begin - len(el._container_begin_repr()) -len(str(i)))
repr_middle = el._container_middle_repr()
repr_middle_adj = repr_middle + " "*(maxlen_middle - len(repr_middle)) #pretty pritting adjustment middle text
if maxlen_middle > 0 + len(str(len(self.containerList))):
s += f"{i}: {name_repr} | {repr_middle_adj} "
else:
s += f"{i}: {name_repr}"
if not end_is_empty:
s += f" | {el._container_end_repr()} \n"
else:
s += "\n"
return s
def __getitem__(self,arg):
if (type(arg) == int):
return self.containerList[arg]
if (type(arg) == slice):
new_container = Container(self.idDict)
items_to_select = self.containerList[arg]
for k,v in self.containerDict.items():
if v in items_to_select:
new_container.containerDict[k] = v
for el in self.containerList:
new_container.containerList = items_to_select
if self.idDict:
for k,v in self.dictIds.items():
if v in items_to_select:
new_container.dictIds[k] = v
return new_container
if type(arg) == str:
return self.containerDict[arg]
def __setitem__(self,k,v):
if (type(k) == int) | (type(k) == slice):
self.containerList[k] = v
return
if type(k) == str:
self.containerDict[k] = v
return
raise(TypeError("Type of key is not supported"))
def __iter__(self):
return iter(self.containerList)
def __len__(self):
return len(self.containerList)
def __add__(self,other):
new_container = Container(self.idDict)
for k,v in self.containerDict.items():
new_container.containerDict[k] = v
for el in self.containerList:
new_container.containerList.append(el)
for k,v in other.containerDict.items():
new_container.containerDict[k] = v
for el in other.containerList:
new_container.containerList.append(el)
if self.idDict and other.idDict:
for k,v in other.dictIds.items():
new_container.dictIds[k] = v
for k,v in self.dictIds.items():
new_container.dictIds[k] = v
return new_container
[docs]
def keys(self):
return self.containerDict.keys()
[docs]
def items(self):
return self.containerDict.items()
[docs]
def values(self):
return self.containerDict.values()
[docs]
def find(self,key,pattern,return_children=False,case_insentive=True):
"""
Parameters
----------
key : str
Name of the attribute or the attribute of a child element that you want to look for
One can search child elements by using the dot-notation.
E.g.: mod["BE"]["BE_2023"].policies.find("functions.name","BenCalc")
pattern : str
pattern that you want to match.
return_children : bool, optional
When True, the return type will be a Container containing elements of the type for which the find method was used
When False, the return type will be a Container of the elements of the deepest level specified by the pattern key-word.
E.g.: mod["BE"]["BE_2023"].policies.find("function)
The default is False.
case_insentive : bool, optional
DESCRIPTION. The default is True.
Returns
-------
TYPE
DESCRIPTION.
"""
return _find(self,key,pattern,return_children,case_insentive=True)
def _find(container,key,pattern,return_children=False,case_insentive=True):
if len(container) == 0:
return Container()
if case_insentive:
flags = re.I
else:
flags = 0
matches = Container()
if "." in key:
idx_dot = key.find(".") #If there is a dot then what is before could be a container
potential_container = key[:idx_dot]
if hasattr(container[0],potential_container) and isinstance(getattr(container[0],potential_container), Container):
for el in container:
if not hasattr(el,potential_container):
continue
matches_children = _find( getattr(el,potential_container),key[idx_dot+1:],pattern,return_children,case_insentive)
if return_children:
matches += matches_children
else:
if len(matches_children) > 0:
matches.add(el.ID,el)
elif not hasattr(container[0],potential_container):
raise AttributeError(f"There is no attribute of the type Container with the name {potential_container}")
else:
for item in container.containerList:
if re.search(pattern, getattr(item,key),flags=flags):
matches.add(item.name,item)
return matches