Best Python code snippet using pandera_python
metavar.py
Source:metavar.py  
1#!/usr/bin/env python2#3# Class to hold all information on a CCPP metadata variable4#5# Python library imports6from __future__ import print_function7import re8import xml.etree.ElementTree as ET9from collections import OrderedDict10# CCPP framework imports11from parse_tools import check_fortran_ref, check_fortran_type, context_string12from parse_tools import FORTRAN_DP_RE, FORTRAN_ID13from parse_tools import registered_fortran_ddt_name14from parse_tools import check_dimensions, check_cf_standard_name15from parse_tools import ParseContext, ParseSource16from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError17###############################################################################18real_subst_re = re.compile(r"(.*\d)p(\d.*)")19list_re = re.compile(r"[(]([^)]*)[)]\s*$")20########################################################################21def standard_name_to_long_name(prop_dict, context=None):22########################################################################23    """Translate a standard_name to its default long_name24    >>> standard_name_to_long_name({'standard_name':'cloud_optical_depth_layers_from_0p55mu_to_0p99mu'})25    'Cloud optical depth layers from 0.55mu to 0.99mu'26    >>> standard_name_to_long_name({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL27    Traceback (most recent call last):28    CCPPError: No standard name to convert foo to long name29    >>> standard_name_to_long_name({}) #doctest: +IGNORE_EXCEPTION_DETAIL30    Traceback (most recent call last):31    CCPPError: No standard name to convert to long name32    >>> standard_name_to_long_name({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL33    Traceback (most recent call last):34    CCPPError: No standard name to convert foo to long name at foo.F90:335    >>> standard_name_to_long_name({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL36    Traceback (most recent call last):37    CCPPError: No standard name to convert to long name at foo.F90:338    """39    # We assume that standar_name has been checked for validity40    # Make the first char uppercase and replace each underscore with a space41    if 'standard_name' in prop_dict:42        standard_name = prop_dict['standard_name']43        if len(standard_name) > 0:44            long_name = standard_name[0].upper() + re.sub("_", " ", standard_name[1:])45        else:46            long_name = ''47        # End if48        # Next, substitute a decimal point for the p in [:digit]p[:digit]49        match = real_subst_re.match(long_name)50        while match is not None:51            long_name = match.group(1) + '.' + match.group(2)52            match = real_subst_re.match(long_name)53        # End while54    else:55        long_name = ''56        if 'local_name' in prop_dict:57            lname = ' {}'.format(prop_dict['local_name'])58        else:59            lname = ''60        # End if61        ctxt = context_string(context)62        raise CCPPError('No standard name to convert{} to long name{}'.format(lname, ctxt))63    # End if64    return long_name65########################################################################66def default_kind_val(prop_dict, context=None):67########################################################################68    """Choose a default kind based on a variable's type69    >>> default_kind_val({'type':'REAL'})70    'kind_phys'71    >>> default_kind_val({'type':'complex'})72    'kind_phys'73    >>> default_kind_val({'type':'double precision'})74    'kind_phys'75    >>> default_kind_val({'type':'integer'})76    ''77    >>> default_kind_val({'type':'character'})78    ''79    >>> default_kind_val({'type':'logical'})80    ''81    >>> default_kind_val({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL82    Traceback (most recent call last):83    CCPPError: No type to find default kind for foo84    >>> default_kind_val({}) #doctest: +IGNORE_EXCEPTION_DETAIL85    Traceback (most recent call last):86    CCPPError: No type to find default kind87    >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL88    Traceback (most recent call last):89    CCPPError: No type to find default kind for foo at foo.F90:390    >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL91    Traceback (most recent call last):92    CCPPError: No type to find default kind at foo.F90:393    """94    if 'type' in prop_dict:95        vtype = prop_dict['type'].lower()96        if vtype == 'real':97            kind = 'kind_phys'98        elif vtype == 'complex':99            kind = 'kind_phys'100        elif FORTRAN_DP_RE.match(vtype) is not None:101            kind = 'kind_phys'102        else:103            kind = ''104        # End if105    else:106        kind = ''107        if 'local_name' in prop_dict:108            lname = ' {}'.format(prop_dict['local_name'])109        else:110            lname = ''111        # End if112        ctxt = context_string(context)113        raise CCPPError('No type to find default kind for {}{}'.format(lname, ctxt))114    # End if115    return kind116########################################################################117def ddt_modules(variable_list):118########################################################################119    ddt_mods = set()120    for var in variable_list:121        if var.is_ddt():122            module = var.get_prop_value('module')123            if len(module) > 0:124                ddt_mods.add((module, var.get_prop_value('type')))125            # End if126        # End if127    # End for128    return ddt_mods129########################################################################130class VariableProperty(object):131    """Class to represent a single property of a metadata header entry132    >>> VariableProperty('local_name', str) #doctest: +ELLIPSIS133    <__main__.VariableProperty object at ...>134    >>> VariableProperty('standard_name', str) #doctest: +ELLIPSIS135    <__main__.VariableProperty object at ...>136    >>> VariableProperty('long_name', str) #doctest: +ELLIPSIS137    <__main__.VariableProperty object at ...>138    >>> VariableProperty('units', str) #doctest: +ELLIPSIS139    <__main__.VariableProperty object at ...>140    >>> VariableProperty('dimensions', list) #doctest: +ELLIPSIS141    <__main__.VariableProperty object at ...>142    >>> VariableProperty('type', str) #doctest: +ELLIPSIS143    <__main__.VariableProperty object at ...>144    >>> VariableProperty('kind', str) #doctest: +ELLIPSIS145    <__main__.VariableProperty object at ...>146    >>> VariableProperty('state_variable', str, valid_values_in=['True',   'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS147    <__main__.VariableProperty object at ...>148    >>> VariableProperty('intent', str, valid_values_in=['in', 'out', 'inout']) #doctest: +ELLIPSIS149    <__main__.VariableProperty object at ...>150    >>> VariableProperty('optional', str, valid_values_in=['True',   'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS151    <__main__.VariableProperty object at ...>152    >>> VariableProperty('local_name', str).name153    'local_name'154    >>> VariableProperty('standard_name', str).type155    <type 'str'>156    >>> VariableProperty('units', str).is_match('units')157    True158    >>> VariableProperty('units', str).is_match('UNITS')159    True160    >>> VariableProperty('units', str).is_match('type')161    False162    >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('2')163    2164    >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('3')165    >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('3', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL166    Traceback (most recent call last):167    CCPPError: Invalid value variable property, '3'168    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('()')169    []170    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x)')171    ['x']172    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('x')173    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x:y)')174    ['x:y']175    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(w:x,y:z)')176    ['w:x', 'y:z']177    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(w:x,x:y:z:q)', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL178    Traceback (most recent call last):179    CCPPError: 'x:y:z:q' is an invalid dimension range180    >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x:3y)', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL181    Traceback (most recent call last):182    CCPPError: '3y' is not a valid Fortran identifier183    """184    __true_vals = ['t', 'true', '.true.']185    __false_vals = ['f', 'false', '.false.']186    def __init__(self, name_in, type_in, valid_values_in=None, optional_in=False, default_in=None, default_fn_in=None, check_fn_in=None):187        self._name = name_in188        self._type = type_in189        if self._type not in [ bool, int, list, str ]:190            raise CCPPError("{} has invalid VariableProperty type, '{}'".format(name_in, type_in))191        # End if192        self._valid_values = valid_values_in193        self._optional = optional_in194        if self.optional:195            if (default_in is None) and (default_fn_in is None):196                raise CCPPError('default_in or default_fn_in is a required property for {} because it is optional'.format(name_in))197            if (default_in is not None) and (default_fn_in is not None):198                raise CCPPError('default_in and default_fn_in cannot both be provided')199            self._default = default_in200            self._default_fn = default_fn_in201        elif default_in is not None:202            raise CCPPError('default_in is not a valid property for {} because it is not optional'.format(name_in))203        elif default_in is not None:204            raise CCPPError('default_fn_in is not a valid property for {} because it is not optional'.format(name_in))205        self._check_fn = check_fn_in206    @property207    def name(self):208        'Return the name of the property'209        return self._name210    @property211    def type(self):212        'Return the type of the property'213        return self._type214    def get_default_val(self, prop_dict, context=None):215        if self._default_fn is not None:216            return self._default_fn(prop_dict, context)217        elif self._default is not None:218            return self._default219        else:220            ctxt = context_string(context)221            raise CCPPError('No default for variable property {}{}'.format(self.name, ctxt))222        # End if223    @property224    def optional(self):225        return self._optional226    def is_match(self, test_name):227        "Return True iff <test_name> is the name of this property"228        return self.name.lower() == test_name.lower()229    def valid_value(self, test_value, error=False):230        'Return a sanitized version of test_value if valid, otherwise return None or abort'231        valid_val = None232        if self.type is int:233            try:234                tv = int(test_value)235                if self._valid_values is not None:236                    if tv in self._valid_values:237                        valid_val = tv238                    else:239                        valid_val = None # i.e. pass240                else:241                    valid_val = tv242            except CCPPError:243                valid_val = None # Redundant but more expressive than pass244        elif self.type is list:245            if isinstance(test_value, str):246                match = list_re.match(test_value)247                if match is None:248                    tv = None249                else:250                    tv = [x.strip() for x in match.group(1).split(',')]251                    if (len(tv) == 1) and (len(tv[0]) == 0):252                        # Scalar253                        tv = list()254                    # End if255                # End if256            else:257                tv = test_value258            # End if259            if isinstance(tv, list):260                valid_val = tv261            elif isinstance(tv, tuple):262                valid_val = list(tv)263            else:264                valid_val = None265            # End if266            if (valid_val is not None) and (self._valid_values is not None):267                # Special case for lists, _valid_values applies to elements268                for item in valid_val:269                    if item not in self._valid_values:270                        valid_val = None271                        break272                    # End if273                # End for274            else:275                pass276        elif self.type is bool:277            if isinstance(test_value, str):278                if test_value.lower() in VariableProperty.__true_vals + VariableProperty.__false_vals:279                    valid_val = test_value.lower() in VariableProperty.__true_vals280                else:281                    valid_val = None # i.e., pass282            else:283                valid_val = not not test_value284        elif self.type is str:285            if isinstance(test_value, str):286                if self._valid_values is not None:287                    if test_value in self._valid_values:288                        valid_val = test_value289                    else:290                        valid_val = None # i.e., pass291                else:292                    valid_val = test_value293                # End if294            # End if295        # End if296        # Call a check function?297        if valid_val and (self._check_fn is not None):298            valid_val = self._check_fn(valid_val, error=error)299        elif (valid_val is None) and error:300            raise CCPPError("Invalid {} variable property, '{}'".format(self.name, test_value))301        # End if302        return valid_val303###############################################################################304class Var(object):305    """ A class to hold a metadata variable306    >>> Var.get_prop('standard_name') #doctest: +ELLIPSIS307    <__main__.VariableProperty object at 0x...>308    >>> Var.get_prop('standard')309    >>> Var.get_prop('type').is_match('type')310    True311    >>> Var.get_prop('type').is_match('long_name')312    False313    >>> Var.get_prop('type').valid_value('character')314    'character'315    >>> Var.get_prop('type').valid_value('char')316    >>> Var.get_prop('long_name').valid_value('hi mom')317    'hi mom'318    >>> Var.get_prop('dimensions').valid_value('hi mom')319    >>> Var.get_prop('dimensions').valid_value(['Bob', 'Ray'])320    ['Bob', 'Ray']321    >>> Var.get_prop('active')322    '.true.'323    >>> Var.get_prop('active').valid_value('flag_for_aerosol_physics')324    'flag_for_aerosol_physics'325    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext())).get_prop_value('long_name')326    'Hi mom'327    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext())).get_prop_value('intent')328    'in'329    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'ttype' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))330    Traceback (most recent call last):331    ParseSyntaxError: Invalid metadata variable property, 'ttype', in <standard input>332    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))333    Traceback (most recent call last):334    ParseSyntaxError: Required property, 'units', missing, in <standard input>335    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'inout', 'constant' : '.true.'}, ParseSource('vname', 'SCHEME', ParseContext())) #doctest: +IGNORE_EXCEPTION_DETAIL336    Traceback (most recent call last):337    ParseSyntaxError: foo is marked constant but is intent inout, at <standard input>:1338    >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'ino'}, ParseSource('vname', 'SCHEME', ParseContext())) #doctest: +IGNORE_EXCEPTION_DETAIL339    Traceback (most recent call last):340    ParseSyntaxError: Invalid intent variable property, 'ino', at <standard input>:1341    """342    # __spec_props are for variables defined in a specification343    __spec_props = [VariableProperty('local_name', str,344                                     check_fn_in=check_fortran_ref),345                    VariableProperty('standard_name', str,346                                     check_fn_in=check_cf_standard_name),347                    VariableProperty('long_name', str, optional_in=True,348                                     default_fn_in=standard_name_to_long_name),349                    VariableProperty('units', str),350                    VariableProperty('dimensions', list,351                                     check_fn_in=check_dimensions),352                    VariableProperty('type', str,353                                     check_fn_in=check_fortran_type),354                    VariableProperty('kind', str,355                                     optional_in=True,356                                     default_fn_in=default_kind_val),357                    VariableProperty('state_variable', bool,358                                     optional_in=True, default_in=False),359                    VariableProperty('constant', bool,360                                     optional_in=True, default_in=False),361                    VariableProperty('allocatable', bool,362                                     optional_in=True, default_in=False),363                    VariableProperty('persistence', str, optional_in=True,364                                     valid_values_in=['timestep', 'run'],365                                     default_in='timestep'),366                    VariableProperty('active', str, optional_in=True,367                                     default_in='.true.')]368    # __var_props contains properties which are not in __spec_props369    __var_props = [VariableProperty('optional', bool,370                                     optional_in=True, default_in=False),371                   VariableProperty('intent', str,372                                    valid_values_in=['in', 'out', 'inout'])]373    __spec_propdict = {}374    __var_propdict = {}375    __required_spec_props = list()376    __required_var_props = list()377    for p in __spec_props:378        __spec_propdict[p.name] = p379        __var_propdict[p.name] = p380        if not p.optional:381            __required_spec_props.append(p.name)382            __required_var_props.append(p.name)383        # End if384    # End for385    for p in __var_props:386        __var_propdict[p.name] = p387        if not p.optional:388            __required_var_props.append(p.name)389        # End if390    # End for391    def __init__(self, prop_dict, source, invalid_ok=False, logger=None):392        """NB: invalid_ok=True is dangerous because it allows creation393        of a Var object with invalid properties.394        In order to prevent silent failures, invalid_ok requires a logger395        in order to take effect."""396        if source.type == 'SCHEME':397            required_props = Var.__required_var_props398            master_propdict = Var.__var_propdict399        else:400            required_props = Var.__required_spec_props401            master_propdict = Var.__spec_propdict402        # End if403        self._source = source404        # Grab a frozen copy of the context405        self._context = ParseContext(context=source.context)406        # First, check the input407        if 'ddt_type' in prop_dict:408            # Special case to bypass normal type rules409            if 'type' not in prop_dict:410                prop_dict['type'] = prop_dict['ddt_type']411            # End if412            if 'units' not in prop_dict:413                prop_dict['units'] = ""414            # End if415            prop_dict['kind'] = prop_dict['ddt_type']416            del prop_dict['ddt_type']417        # End if418        for key in prop_dict:419            if Var.get_prop(key) is None:420                raise ParseSyntaxError("Invalid metadata variable property, '{}'".format(key), context=self.context)421            # End if422        # End for423        # Make sure required properties are present424        for propname in required_props:425            if propname not in prop_dict:426                if invalid_ok and (logger is not None):427                    ctx = context_string(self.context)428                    logger.warning("Required property, '{}', missing{}".format(propname, ctx))429                else:430                    raise ParseSyntaxError("Required property, '{}', missing".format(propname), context=self.context)431                # End if432            # End if433        # End for434        # Check for any mismatch435        if ('constant' in prop_dict) and ('intent' in prop_dict):436            if prop_dict['intent'].lower() != 'in':437                if invalid_ok and (logger is not None):438                    ctx = context_string(self.context)439                    logger.warning("{} is marked constant but is intent {}{}".format(prop_dict['local_name'], prop_dict['intent'], ctx))440                else:441                    raise ParseSyntaxError("{} is marked constant but is intent {}".format(prop_dict['local_name'], prop_dict['intent']), context=self.context)442                # End if443            # End if444        # End if445        # Steal dict from caller446        self._prop_dict = prop_dict447        # Fill in default values for missing properties448        for propname in master_propdict:449            if (propname not in prop_dict) and master_propdict[propname].optional:450                self._prop_dict[propname] = master_propdict[propname].get_default_val(self._prop_dict, context=self.context)451            # End if452        # End for453        # Make sure all the variable values are valid454        try:455            for prop in self._prop_dict.keys():456                check = Var.get_prop(prop).valid_value(self._prop_dict[prop],457                                                       error=True)458            # End for459        except CCPPError as cp:460            if invalid_ok and (logger is not None):461                ctx = context_string(self.context)462                logger.warning("{}: {}{}".format(self._prop_dict['local_name'], cp, ctx))463            else:464                raise ParseSyntaxError("{}: {}".format(self._prop_dict['local_name'], cp),465                                       context=self.context)466            # End if467        # End try468    def compatible(self, other, logger=None):469        # We accept character(len=*) as compatible with character(len=INTEGER_VALUE)470        stype =     self.get_prop_value('type')471        skind =     self.get_prop_value('kind')472        sunits =    self.get_prop_value('units')473        srank=      self.get_prop_value('tank')474        sstd_name = self.get_prop_value('standard_name')475        otype =     other.get_prop_value('type')476        okind =     other.get_prop_value('kind')477        ounits =    other.get_prop_value('units')478        orank=      other.get_prop_value('tank')479        ostd_name = other.get_prop_value('standard_name')480        if stype == 'character':481            kind_eq = ((skind == okind) or482                       (skind == 'len=*' and okind.startswith('len=')) or483                       (skind.startswith('len=') and okind == 'len=*'))484        else:485            kind_eq = skind == okind486        # End if487        if ((sstd_name == ostd_name) and kind_eq and488            (sunits == ounits) and (stype == otype) and (srank == orank)):489            return True490        elif logger is not None:491            if sstd_name != ostd_name:492                logger.info("standard_name: '{}' != '{}'".format(sstd_name, ostd_name))493            elif not kind_eq:494                logger.info("kind: '{}' != '{}'".format(skind, okind))495            elif sunits != ounits:496                logger.info("units: '{}' != '{}'".format(sunits, ounits))497            elif stype != otype:498                logger.info("type: '{}' != '{}'".format(stype, otype))499            elif srank != orank:500                logger.info("rank: '{}' != '{}'".format(srank, orank))501            else:502                logger.error('Why are these variables not compatible?')503            # End if504            return False505        else:506            return False507        # End if508    @classmethod509    def get_prop(cls, name, spec_type=None):510        if (spec_type is None) and (name in Var.__var_propdict):511            return Var.__var_propdict[name]512        elif (spec_type is not None) and (name in Var.__spec_propdict):513            return Var.__spec_propdict[name]514        else:515            return None516    def get_prop_value(self, name):517        if name in self._prop_dict:518            return self._prop_dict[name]519        else:520            return None521    @property522    def context(self):523        return self._context524    @property525    def source(self):526        return self._source527    @classmethod528    def loop_subst_dims(cls, dims):529        newdims = list()530        for dim in dims:531            # loop_subst_match swallows an entire dim string, even ranges532            ldim = VarDictionary.loop_subst_match(dim)533            if ldim is None:534                newdims.append(dim)535            else:536                newdims.append(ldim)537            # End if538        # End for539        return newdims540    def get_dimensions(self, loop_subst=False):541        "Return the variable's dimension string"542        dimval = self.get_prop_value('dimensions')543        dims = Var.get_prop('dimensions').valid_value(dimval)544        if loop_subst:545            newdims = loop_subst_dims(dims)546        else:547            newdims = dims548        # End if549        return newdims550    def write_def(self, outfile, indent, dict, allocatable=False, loop_subst=False):551        '''Write the definition line for the variable.'''552        vtype = self.get_prop_value('type')553        kind = self.get_prop_value('kind')554        name = self.get_prop_value('local_name')555        dims = self.get_dimensions(loop_subst=loop_subst)556        if (dims is not None) and (len(dims) > 0):557            if allocatable:558                dimstr = '(:' + ',:'*(len(dims) - 1) + ')'559            else:560                dimstr = '('561                comma = ''562                for dim in dims:563                    # Only ranges or sizes go into declaration564                    if VarDictionary.loop_var_match(dim):565                        continue566                    else:567                        dstdnames = dim.split(':')568                        dvars = [dict.find_variable(x) for x in dstdnames]569                        if None in dvars:570                            for dim in dstdnames:571                                if dict.find_variable(dim) is None:572                                    raise CCPPError("No variable found for '{}'".format(dim))573                                # End if574                            # End for575                        # End if576                        dnames = [x.get_prop_value('local_name') for x in dvars]577                        dimstr = dimstr + comma + ':'.join(dnames)578                        comma = ', '579                    # End if580                # End for581                dimstr = dimstr + ')'582                if dimstr == '()':583                    dimstr = '' # It ends up being a scalar reference584                # End if585            # End if586        else:587            dimstr = ''588        # End if589        constant = self.get_prop_value('constant')590        intent = self.get_prop_value('intent')591        if constant and allocatable:592            raise CCPPError('Cannot create allocatable variable from constant, {}'.format(name))593        # End if594        if constant:595            intent_str = 'intent(in)   '596        elif allocatable:597            if len(dimstr) > 0:598                intent_str = 'allocatable  '599            else:600                intent_str = ' '*13601            # End if602        elif intent is not None:603            intent_str = 'intent({}){}'.format(intent, ' '*(5 - len(intent)))604        else:605            intent_str = ' '*13606        # End if607        if self.is_ddt():608            str = "type({kind}){cspc}{intent} :: {name}{dims}"609            cspc = ',' + ' '*(13 - len(kind))610        else:611            if (kind is not None) and (len(kind) > 0):612                str = "{type}({kind}){cspc}{intent} :: {name}{dims}"613                cspc = ',' + ' '*(17 - len(vtype) - len(kind))614            else:615                str = "{type}{cspc}{intent} :: {name}{dims}"616                cspc = ',' + ' '*(19 - len(vtype))617            # End if618        # End if619        outfile.write(str.format(type=vtype, kind=kind, intent=intent_str,620                                 name=name, dims=dimstr, cspc=cspc), indent)621    def is_ddt(self):622        '''Return True iff <self> is a DDT type.'''623        vtype = self.get_prop_value('type')624        return registered_fortran_ddt_name(vtype) is not None625    def host_arg_str(self, hvar, host_model, ddt):626        '''Create the proper statement of a piece of a host-model variable.627        If ddt is True, we can only have a single element selected628        '''629        hstr = hvar.get_prop_value('local_name')630        # Turn the dimensions string into a proper list and take the correct one631        hdims = hvar.get_dimensions()632        dimsep = ''633        # Does the local name have any extra indices?634        match = array_ref_re.match(hstr.strip())635        if match is not None:636            hstr = match.group(1)637            # Find real names for all the indices638            tokens = [x.strip() for x in match.group(2).strip().split(',')]639            for token in tokens:640                hsdim = self.find_host_model_var(token, host_model)641                dimstr = dimstr + dimsep + hsdim642            # End for643        # End if644        if len(hdims) > 0:645            dimstr = '('646        else:647            dimstr = ''648        # End if649        for hdim in hdims:650            if ddt and (':' in hdim):651                raise CCPPError("Invalid DDT dimension spec {}({})".format(hstr, hdimval))652            else:653                # Find the host model variable for each dim654                hsdims = self.find_host_model_var(hdim, host_model)655                dimstr = dimstr + dimsep + hsdims656                dimsep = ', '657            # End if658        # End for659        if len(hdims) > 0:660            dimstr = dimstr + ')'661        # End if662        return hstr + dimstr663    def print_debug(self):664        '''Print the data retrieval line for the variable.'''665        str='''Contents of {local_name} (* = mandatory for compatibility):666        standard_name = {standard_name} *667        long_name     = {long_name}668        units         = {units} *669        local_name    = {local_name}670        type          = {type} *671        dimensions    = {dimensions} *672        kind          = {kind} *673'''674        if 'intent' in self.__spec_propdict.keys():675            str += '        intent        = {intent}\n'676        if 'optional' in self.__spec_propdict.keys():677            str += '        optional      = {optional}\n'678        if self._context is not None:679            str += '        context       = {}'.format(self._context)680        # End if681        return str.format(**self._prop_dict)682    def __str__(self):683        '''Print representation or string for Var objects'''684        return "<Var {standard_name}: {local_name}>".format(**self._prop_dict)685    def __repr__(self):686        '''Object representation for Var objects'''687        base = super(Var, self).__repr__()688        pind = base.find(' object ')689        if pind >= 0:690            pre = base[0:pind]691        else:692            pre = '<Var'693        # End if694        bind = base.find('at 0x')695        if bind >= 0:696            post = base[bind:]697        else:698            post = '>'699        # End if700        return '{} {}: {} {}'.format(pre, self._prop_dict['standard_name'], self._prop_dict['local_name'], post)701###############################################################################702class VarSpec(object):703    """A class to hold a standard_name description of a variable.704    A scalar variable is just a standard name while an array also705    contains a comma-separated list of dimension standard names in parentheses.706    """707    def __init__(self, var, loop_subst=False):708        self._name = var.get_prop_value('standard_name')709        self._dims = var.get_dimensions(loop_subst=loop_subst)710        if len(self._dims) == 0:711            self._dims = None712        # End if713    @property714    def name(self):715        return self._name716    def get_dimensions(self, loop_subst=False):717        if loop_subst:718            rdims = Var.loop_subst_dims(dims)719        else:720            rdims = dims721        # End if722        return rdims723    def __repr__(self):724        if self._dims is not None:725            return "{}({})".format(self._name, ', '.join(self._dims))726        else:727            return self._name728        # End if729###############################################################################730class VarDDT(Var):731    """A class to store a variable that is a component of a DDT (at any732    DDT nesting level).733    """734    def __init__(self, standard_name, var_ref_list, logger=None):735        self._standard_name = standard_name736        self._var_ref_list = list()737        for var in var_ref_list:738            self._var_ref_list.append(var)739        # End for740        self._vlen = len(self._var_ref_list)741        if logger is not None:742            lnames = [x.get_prop_value('local_name') for x in self._var_ref_list]743            logger.debug('Adding DDT field, {}, {}'.format(standard_name, lnames))744        # End if745    def compatible(self, other, logger=None):746        "Compare <other> to the intrinsic variable the end of the DDT chain"747        self._var_ref_list[-1].compare(other)748    def get_prop_value(self, name, index=0):749        "Return the indicated property value, defauling to the top-level DDT"750        if abs(index) >= self._vlen:751            raise ParseInternalError("VarDDT.get_prop_value index ({}) out of range".format(index))752        # End if753        return self._var_ref_list[index].get_prop_value(name)754    @property755    def context(self):756        "Return the context of the variable source (DDT root)"757        return self._var_ref_list[0].context758    @property759    def source(self):760        "Return the source of the variable source (DDT root)"761        return self._var_ref_list[0].source762    def get_dimensions(self, loop_subst=False, index=0):763        "Return the dimensions of the indicated var, defauling to the top-level DDT"764        if abs(index) >= self._vlen:765            raise ParseInternalError("VarDDT.get_prop_value index ({}) out of range".format(index))766        # End if767        return self._var_ref_list[index].get_dimensions(loop_subst)768    def write_def(self, outfile, indent, dict, allocatable=False, loop_subst=False):769        '''Write the definition line for the variable.'''770        pass771    def is_ddt(self):772        '''Return True iff <self> is a DDT type.'''773        return True774    def host_arg_str(self, hvar, host_model, ddt):775        '''Create the proper statement of a piece of a host-model variable.776        If ddt is True, we can only have a single element selected777        '''778        pass779    def print_debug(self):780        for var in self._var_ref_list:781            var.print(debug)782        # End for783    def __repr__(self):784        '''Print representation or string for VarDDT objects'''785        return "<{}>".format('%'.join([x.__repr__() for x in self._var_ref_list]))786###############################################################################787class VarDictionary(OrderedDict):788    """789    A class to store and cross-check variables from one or more metadata790    headers. The class also serves as a scoping construct so that a variable791    can be found in an innermost available scope.792    The dictionary is organized by standard_name. It is an error to try793    to add a variable if its standard name is already in the dictionary.794    Scoping is a tree of VarDictionary objects.795    >>> VarDictionary('foo')796    VarDictionary(foo)797    >>> VarDictionary('bar', variables={})798    VarDictionary(bar)799    >>> VarDictionary('baz', Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))) #doctest: +ELLIPSIS800    VarDictionary(baz, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)])801    >>> print("{}".format(VarDictionary('baz', Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext())))))802    VarDictionary(baz, ['hi_mom'])803    >>> VarDictionary('qux', [Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))]) #doctest: +ELLIPSIS804    VarDictionary(qux, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)])805    >>> VarDictionary('boo').add_variable(Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext())))806    >>> VarDictionary('who', variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))]).prop_list('local_name')807    ['foo']808    >>> VarDictionary('glitch', Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'SCHEME', ParseContext()))).add_variable(Var({'local_name' : 'bar', 'standard_name' : 'hi_mom', 'units' : 'm/s', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname2', 'DDT', ParseContext()))) #doctest: +IGNORE_EXCEPTION_DETAIL809    Traceback (most recent call last):810    ParseSyntaxError: Invalid Duplicate standard name, 'hi_mom', at <standard input>:811    """812    # Loop variables813    __ccpp_loop_vars__ = ['horizontal_loop_begin', 'horizontal_loop_end',814                          'thread_block_number', 'horizontal_loop_extent']815    # Loop substitutions816    __ccpp_loop_subst__ = {'horizontal_loop_extent' :817                           ('horizontal_loop_begin', 'horizontal_loop_end'),818                           'thread_block_begin:thread_block_end' :819                           'thread_block_number'}820    # Dimension substitutions821    __ccpp_dim_subst__ = {'horizontal_loop_extent' : 'horizontal_dimension'}822    # Variable representing the constant integer, 1823    __var_one = Var({'local_name' : 'ccpp_one', 'constant' : 'True',824                     'standard_name' : 'ccpp_constant_one',825                     'units' : '1', 'dimensions' : '()', 'type' : 'integer'},826                    ParseSource('VarDictionary', 'REGISTRY', ParseContext()))827    def __init__(self, name, variables=None, parent_dict=None, logger=None):828        "Unlike dict, VarDictionary only takes a Var or Var list"829        super(VarDictionary, self).__init__()830        self._name = name831        self._logger = logger832        self._parent_dict = parent_dict833        if parent_dict is not None:834            parent_dict.add_sub_scope(self)835        # End if836        self._sub_dicts = list()837        if isinstance(variables, Var):838            self.add_variable(variables)839        elif isinstance(variables, list):840            for var in variables:841                self.add_variable(var)842            # End for843        elif isinstance(variables, VarDictionary):844            for stdname in variables.keys():845                self[stdname] = variables[stdname]846            # End for847        elif isinstance(variables, dict):848            # variables will not be in 'order', but we accept them anyway849            for stdname in variables.keys():850                self[stdname] = variables[stdname]851            # End for852        elif variables is not None:853            raise ParseInternalError('Illegal type for variables, {} in {}'.format(type(variables), self.name))854        # End if855    @property856    def name(self):857        return self._name858    @property859    def parent(self):860        return self._parent_dict861    def include_var_in_list(self, var, std_vars, loop_vars, consts):862        '''Return True iff <var> is of a type allowed by the logicals,863        <std_vars> (not constants or loop_vars),864        <loop_vars> a variable ending in "_extent", "_begin", "_end", or865        <consts> a variable with the "constant" property.866        '''867        const_val = var.get_prop_value('constant')868        const_var = Var.get_prop('constant').valid_value(const_val)869        include_var = consts and const_var870        if not include_var:871            standard_name = var.get_prop_value('standard_name')872            loop_var = VarDictionary.loop_var_match(standard_name)873            include_var = loop_var and loop_vars874            if not include_var:875                std_var = not (loop_var or const_var)876                include_var = std_vars and std_var877            # End if878        # End if879        return include_var880    def variable_list(self, recursive=False,881                      std_vars=True, loop_vars=True, consts=True):882        "Return a list of all variables"883        if recursive and (self._parent_dict is not None):884            vlist = self._parent_dict.variable_list(recursive=recursive,885                                                    std_vars=std_vars,886                                                    loop_vars=loop_vars,887                                                    consts=consts)888        else:889            vlist = list()890        # End if891        for sn in self.keys():892            var = self[sn]893            if self.include_var_in_list(var, std_vars=std_vars,894                                        loop_vars=loop_vars, consts=consts):895                vlist.append(var)896            # End if897        # End for898        return vlist899    def add_variable(self, newvar, exists_ok=False):900        """Add a variable if it does not conflict with existing entries"""901        standard_name = newvar.get_prop_value('standard_name')902        if (standard_name in self) and (not exists_ok):903            # We already have a matching variable, error!904            if self._logger is not None:905                self._logger.error("Attempt to add duplicate variable, {} from {}".format(standard_name, newvar.source.name))906            # End if907            raise ParseSyntaxError("Duplicate standard name in {}".format(self.name),908                                   token=standard_name, context=newvar._context)909        # End if910        cvar = self.find_variable(standard_name)911        if (cvar is not None) and (not cvar.compatible(newvar, self._logger)):912            if self._logger is not None:913                self._logger.error("Attempt to add incompatible variable, {} from {}".format(standard_name, newvar.source.name))914            # End if915            errstr = "standard name, incompatible with {}"916            raise ParseSyntaxError(errstr.format(cvar.context),917                                   token=standard_name,918                                   context=newvar.source.context)919        # End if920        # If we make it to here without an exception, add the variable921        self[standard_name] = newvar922    def remove_variable(self, standard_name):923        """Remove <standard_name> from the dictionary.924        Ignore if <standard_name> is not in dict925        """926        if standard_name in self:927            del self[standard_name]928        # End if929    def find_variable(self, standard_name, any_scope=True, loop_subst=False):930        """Return the variable matching <standard_name> or None931        If any_scope is True, search parent scopes if not in current scope.932        """933        if standard_name in self:934            var = self[standard_name]935        elif any_scope and (self._parent_dict is not None):936            var = self._parent_dict.find_variable(standard_name, any_scope)937        else:938            var = None939        # End if940        if (var is None) and loop_subst:941            var = self.find_loop_subst(standard_name, any_scope=any_scope)942        # End if943        return var944    def add_sub_scope(self, sub_dict):945        'Add a child dictionary to enable traversal'946        self._sub_dicts.append(sub_dict)947    def prop_list(self, prop_name, std_vars=True, loop_vars=True, consts=True):948        '''Return a list of the <prop_name> property for each variable.949        std_vars are variables which are neither constants nor loop variables.950        '''951        plist = list()952        for standard_name in self.keys():953            var = self.find_variable(standard_name, any_scope=False, loop_subst=False)954            if self.include_var_in_list(var, std_vars=std_vars, loop_vars=loop_vars, consts=consts):955                plist.append(self[standard_name].get_prop_value(prop_name))956            # End if957        # End for958        return plist959    def declare_variables(self, outfile, indent,960                          std_vars=True, loop_vars=True, consts=True):961        "Write out the declarations for this dictionary's variables"962        for standard_name in self.keys():963            var = self.find_variable(standard_name, any_scope=False, loop_subst=False)964            if self.include_var_in_list(var, std_vars=std_vars, loop_vars=loop_vars, consts=consts):965                self[standard_name].write_def(outfile, indent, self)966            # End if967        # End for968    def merge(self, other_dict):969        "Add new entries from <other_dict>"970        for ovar in other_dict.variable_list():971            self.add_variable(ovar)972        # End for973    def __str__(self):974        return "VarDictionary({}, {})".format(self.name, self.keys())975    def __repr__(self):976        srepr = super(VarDictionary, self).__repr__()977        vstart = len("VarDictionary") + 1978        if len(srepr) > vstart + 1:979            comma = ", "980        else:981            comma = ""982        # End if983        return "VarDictionary({}{}{}".format(self.name, comma, srepr[vstart:])984    def __del__(self):985        try:986            for key in self.keys():987                del self[key]988            # End for989        except Exception as e:990            pass # python does not guarantee object state during finalization991        # End try992    @classmethod993    def loop_var_match(cls, standard_name):994        'Return True iff <standard_name> is a loop variable'995        return standard_name in cls.__ccpp_loop_vars__996    @classmethod997    def loop_subst_match(cls, standard_name):998        'Return a loop substitution match, if any, for <standard_name>'999        if standard_name in cls.__ccpp_loop_subst__:1000            return cls.__ccpp_loop_subst__[standard_name]1001        else:1002            return None1003        # End if1004    def find_loop_subst(self, standard_name, any_scope=True, context=None):1005        """If <standard_name> is of the form <standard_name>_extent and that1006        variable is not in the dictionary, substitute a tuple of variables,1007        (<standard_name>_begin, <standard_name>_end), if those variables are1008        in the dictionary.1009        If <standard_name>_extent *is* present, return that variable as a1010        range, (__var_one, <standard_name>_extent)1011        In other cases, return None1012        """1013        loop_var = VarDictionary.loop_subst_match(standard_name)1014        logger_str = None1015        if loop_var is not None:1016            # Let us see if we can fix a loop variable1017            dict_var = self.find_variable(standard_name,1018                                          any_scope=any_scope, loop_subst=False)1019            if dict_var is not None:1020                my_var = (VarDictionary.__var_one, dict_var)1021                if self._logger is not None:1022                    logger_str = "loop_subst: found {}{}".format(standard_name, context_string(context))1023                # End if1024            else:1025                my_vars = [self.find_variable(x) for x in loop_var]1026                if None not in my_vars:1027                    my_var = tuple(my_vars)1028                    if self._logger is not None:1029                        names = [x.get_prop_value('local_name') for x in my_vars]1030                        logger_str = "loop_subst: {} ==> (){}".format(standard_name, ', '.join(names), context_string(context))1031                    # End if1032                else:1033                    if self._logger is not None:1034                        logger_str = "loop_subst: {} ==> ({}, {}) FAILED{}".format(standard_name, beg_name, end_name, context_string(context))1035                    # End if1036                    my_var = None1037                # End if1038            # End if1039        else:1040            if self._logger is not None:1041                logger_str = "loop_subst: {} is not a loop variable{}".format(standard_name, context_string(context))1042            # End if1043            my_var = None1044        # End if1045        if logger_str is not None:1046            self._logger.debug(logger_str)1047        # End if1048        return my_var1049    def find_dimension_subst(self, standard_name, any_scope=True, context=None):1050        """If <standard_name> is of the form <standard_name>_loop_extent1051        attempt to find a variable of the form <standard_name>_dimension1052        and return that. If such a variable is not found, raise an exception.1053        If <standard_name> is not of the form <standard_name>_extent, return1054        None.1055        """1056        loop_var = standard_name in VarDictionary.__ccpp_dim_subst__1057        logger_str = None1058        if loop_var:1059            # Let us see if we can replace the variable1060            dim_name = VarDictionary.__ccpp_dim_subst__[standard_name]1061            my_var = self.find_variable(dim_name, any_scope=any_scope)1062            if my_var is None:1063                raise CCPPError("Dimension variable, {} not found{}".format(dim_name, context_string(context)))1064            # End if1065        else:1066            my_var = None1067        # End if1068        return my_var1069###############################################################################1070if __name__ == "__main__":1071    import doctest...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
