0

While I am able to fix this and it is not a huge issue. I am curious if I am doing something wrong or went about something wrong. I inherited a codebase that is python2 to upgrade it to python3. So I just bit the bullet and dug in, started with a 2to3 conversion. Now bit by bit I am running and seeing where it croaks, fixing it and rinse repeat. I noticed most the issues are in the module imports from local modules created.

An example:

a file in a folder called Label.py has the line:

__all__ = ['Label', 'BoolLabel', 'StrLabel', 'IntLabel', 'FloatLabel']

class Label(tkinter.Label, CtxMenuMixin, IsCurrentMixin, SeverityMixin):
    ...code...
    ...code...

class FloatLabel(Label):
    ...code...
    ...code...

This file is used by another one called GrayImageDispWdg.py and in that it is trying to do a call as such:

self.currXPosWdg = Label.FloatLabel()

This USED to work in python2.7 now though after the conversion I get a:

type object 'Label' has no attribute 'FloatLabel'

Now I can fix this at the top of say GrayImageDispWdg.py file by instead of saying just this:

from . import Label

if I also say:

from . import FloatLabel

and then change the line:

self.currXPosWdg = Label.FloatLabel()

to

self.currXPosWdg = FloatLabel()

Then it seems to work, but I am doing this a ton on all files since the conversion. Maybe I had something wrong setup? Maybe was some sort of path issue I missed? (I am not a Python guru by any means whilst editing / converting this stuff either)

__all__ = ['Label', 'BoolLabel', 'StrLabel', 'IntLabel', 'FloatLabel', 'DMSLabel']

import sys
import tkinter
import RO.Constants
import RO.MathUtil
import RO.StringUtil
from . import CtxMenu
from . import CtxMenuMixin
from .SeverityMixin import SeverityMixin
from .IsCurrentMixin import IsCurrentMixin

class Label(tkinter.Label, CtxMenuMixin, IsCurrentMixin, SeverityMixin):
    """Base class for labels (display ROWdgs); do not use directly.
    
    Inputs:
    - formatStr: formatting string; if omitted, formatFunc is used.
        Displayed value is formatStr % value.
    - formatFunc: formatting function; ignored if formatStr specified.
        Displayed value is formatFunc(value).
    - helpText  short text for hot help
    - helpURL   URL for on-line help
    - isCurrent is value current?
    - severity  one of RO.Constants.sevNormal, sevWarning or sevError
    - **kargs: all other keyword arguments go to Tkinter.Label;
        the defaults are anchor="e", justify="right"
        
    Inherited methods include:
    getIsCurrent, setIsCurrent
    getSeverity, setSeverity
        
    Note: if display formatting fails (raises an exception)
    then "?%r?" % value is displayed.
    """

    def __init__ (self,
        master,
        formatStr = None,
        formatFunc = str,       
        helpText = None,
        helpURL = None,
        isCurrent = True,
        severity = RO.Constants.sevNormal,
    **kargs):
        kargs.setdefault("anchor", "e")
        kargs.setdefault("justify", "right")
        
        tkinter.Label.__init__(self, master, **kargs)
        
        CtxMenuMixin.__init__(self, helpURL=helpURL)
        
        IsCurrentMixin.__init__(self, isCurrent)
        
        SeverityMixin.__init__(self, severity)

        self._formatStr = formatStr
        if formatStr != None:
            formatFunc = self._formatFromStr
        self._formatFunc = formatFunc
        self.helpText = helpText

        self._value = None

    def get(self):
        """Return a tuple consisting of (set value, isCurrent).
        
        If the value is None then it is invalid or unknown.
        If isCurrent is false then the value is suspect
        Otherwise the value is valid and current.
        """
        return (self._value, self._isCurrent)
    
    def getFormatted(self):
        """Return a tuple consisting of the (displayed value, isCurrent).
        
        If the value is None then it is invalid.
        If isCurrent is false then the value is suspect
        Otherwise the value is valid and current.
        """
        if self._value == None:
            return (None, self._isCurrent)
        else:
            return (self["text"], self._isCurrent)
    
    def clear(self, isCurrent=1):
        """Clear the display; leave severity unchanged.
        """
        self.set(value="", isCurrent=isCurrent)
    
    def set(self,
        value,
        isCurrent = True,
        severity = None,
    **kargs):
        """Set the value

        Inputs:
        - value: the new value
        - isCurrent: is value current (if not, display with bad background color)
        - severity: the new severity, one of: RO.Constants.sevNormal, sevWarning or sevError;
          if omitted, the severity is left unchanged          
        kargs is ignored; it is only present for compatibility with KeyVariable callbacks.
        
        Raises an exception if the value cannot be coerced.
        """
        # print "RO.Wdg.Label.set called: value=%r, isCurrent=%r, **kargs=%r" % (value, isCurrent, kargs)
        self._value = value
        self.setIsCurrent(isCurrent)
        if severity != None:
            self.setSeverity(severity)
        self._updateText()
    
    def setNotCurrent(self):
        """Mark the data as not current.
        
        To mark the value as current again, set a new value.
        """
        self.setIsCurrent(False)
    
    def _formatFromStr(self, value):
        """Format function based on formatStr.
        """
        return self._formatStr % value

    def _updateText(self):
        """Updates the displayed value. Ignores isCurrent and severity.
        """
        if self._value == None:
            self["text"] = ""
        else:
            try:
                self["text"] = self._formatFunc(self._value)
            except Exception as e:
                sys.stderr.write("format of value %r failed with error: %s\n" % (self._value, e))
                self["text"] = "?%r?" % (self._value,)


class BoolLabel(Label):
    """Label to display string data.
    Inputs are those for Label, but formatStr and formatFunc are forbidden.
    """
    def __init__ (self,
        master,
        helpText = None,
        helpURL = None,
        trueValue = "True",
        falseValue = "False",
        isCurrent = True,
        **kargs
    ):
        assert "formatStr" not in kargs, "formatStr not allowed for %s" % self.__class__.__name__
        assert "formatFunc" not in kargs, "formatFunc not allowed for %s" % self.__class__.__name__

        def formatFnct(val):
            if val:
                return trueValue
            else:
                return falseValue

        Label.__init__(self,
            master,
            formatFunc = formatFnct,
            helpText = helpText,
            helpURL = helpURL,
            isCurrent = isCurrent,
        **kargs)


class StrLabel(Label):
    """Label to display string data.
    Inputs are those for Label but the default formatFunc is str.
    """
    def __init__ (self,
        master,
        helpText = None,
        helpURL = None,
        isCurrent = True,
        **kargs
    ):
        kargs.setdefault("formatFunc", str)
        
        Label.__init__(self,
            master,
            helpText = helpText,
            helpURL = helpURL,
            isCurrent = isCurrent,
        **kargs)


class IntLabel(Label):
    """Label to display integer data; truncates floating point data
    Inputs are those for Label, but the default formatStr is "%s" and formatFunc is forbidden.
    """
    def __init__ (self,
        master,
        helpText = None,
        helpURL = None,
        isCurrent = True,
        **kargs
    ):
        kargs.setdefault("formatStr", "%d")
        assert "formatFunc" not in kargs, "formatFunc not allowed for %s" % self.__class__.__name__
        
        Label.__init__(self,
            master,
            helpText = helpText,
            helpURL = helpURL,
            isCurrent = isCurrent,
        **kargs)


class FloatLabel(Label):
    """Label to display floating point data.
    
    If you specify a format string, that is used and the specified is ignored
    else you must specify a precision, in which case the data is displayed
    as without an exponent and with "precision" digits past the decimal.
    The default precision is 2 digits.
    
    Inputs:
    - precision: number of digits past the decimal point; ignored if formatStr specified
    The other inputs are those for Label but formatFunc is forbidden.
    """
    def __init__ (self,
        master,
        formatStr=None,
        precision=2,
        helpText = None,
        helpURL = None,
        isCurrent = True,
    **kargs):
        assert "formatFunc" not in kargs, "formatFunc not allowed for %s" % self.__class__.__name__

        # handle default format string
        if formatStr == None:
            formatStr = "%." + str(precision) + "f"
            
        # test and set format string
        try:
            formatStr % (1.1,)
        except:
            raise ValueError("Invalid floating point format string %s" % (formatStr,))

        Label.__init__(self,
            master,
            formatStr = formatStr,
            helpText = helpText,
            helpURL = helpURL,
            isCurrent = isCurrent,
        **kargs)


class DMSLabel(Label):
    """Label to display floating point data as dd:mm:ss.ss.
    Has the option to store data in degrees but display in hh:mm:ss.ss;
    this option can be changed at any time and the display updates correctly.
    
    Inputs:
    - precision: number of digits past the decimal point
    - nFields: number of sexagesimal fields to display
    - cnvDegToHrs: if True, data is in degrees but display is in hours
    The other inputs are those for Label, but formatStr and formatFunc are forbidden.
    """
    def __init__ (self,
        master,
        precision,
        nFields = 3,
        cvtDegToHrs = False,
        helpText = None,
        helpURL = None,
        isCurrent = True,
    **kargs):
        assert "formatStr" not in kargs, "formatStr not allowed for %s" % self.__class__.__name__
        assert "formatFunc" not in kargs, "formatFunc not allowed for %s" % self.__class__.__name__
        
        self.precision = precision
        self.nFields = nFields
        self.cvtDegToHrs = cvtDegToHrs

        Label.__init__(self,
            master,
            formatFunc = self.formatFunc,
            helpText = helpText,
            helpURL = helpURL,
            isCurrent = isCurrent,
        **kargs)
    
    def formatFunc(self, value):
        if self.cvtDegToHrs and value != None:
            value = value / 15.0
        return RO.StringUtil.dmsStrFromDeg (
            value,
            precision = self.precision,
            nFields = self.nFields,
        )
    
    def setCvtDegToHrs(self, cvtDegToHrs):
        if RO.MathUtil.logNE(self.cvtDegToHrs, cvtDegToHrs):
            self.cvtDegToHrs = cvtDegToHrs
            self._updateText()


if __name__ == "__main__":
    from . import PythonTk
    from RO.TkUtil import Timer
    root = PythonTk.PythonTk()

    wdgSet = (
        BoolLabel(root,
            helpText = "Bool label",
        ),
        StrLabel(root,
            helpText = "String label",
        ),
        IntLabel(root,
            width=5,
            helpText = "Int label; width=5",
        ),
        FloatLabel(root,
            precision=2,
            width=5,
            helpText = "Float label; precision = 2, width=5",
        ),
        FloatLabel(root,
            formatStr="%.5g",
            width=8,
            helpText = "Float label; format = '\%.5g', width = 8",
        ),
        DMSLabel(root,
            precision=2,
            width=10,
            helpText = "DMS label; precision = 2, width = 10",
        ),
        DMSLabel(root,
            precision=2,
            cvtDegToHrs=1,
            width=10,
            helpText = "DMS label; precision = 2, width = 10, convert degrees to hours",
        ),
    )
    for wdg in wdgSet:
        wdg.pack(fill=tkinter.X)
    
    # a list of (value, isCurrent) pairs
    testData = [
        ("some text", True),
        ("invalid text", False),
        (0, True),
        ("", True),
        (False, True),
        (1, True),
        (1234567890, True),
        (1234567890, False),
        (1.1, True),
        (1.9, True),
        (-1.1, True),
        (-1.9, True),
        (-0.001, True),
        (-1.9, False),
    ]
    
    ind = 0
    def displayNext():
        global ind, testData
        val = testData[ind]
        print("\nvalue = %r, isCurrent = %s" % tuple(val))
        for wdg in wdgSet:
            wdg.set(*val)
        ind += 1
        if ind < len(testData):
            Timer(1.2, displayNext)
    Timer(1.2, displayNext)
            
    root.mainloop()
Codejoy
  • 3,722
  • 13
  • 59
  • 99
  • If `Label` itself doesn't raise a `NameError`, then any change in how `Label.FloatLabel` behaves is not (directly) an import issue, but a change in how `Label` is defined. How did `Label` receive a class attribute named `FloatLabel` in the first place? I also suspect some confusion between the *module* `Label` and the *class* `Label` defined in that module. – chepner Jan 12 '22 at 18:43
  • I don't know of anything specific that `2to3` might have done to make a difference. – chepner Jan 12 '22 at 18:44
  • Well it might not be from the 2to3 but the code was doing something python2 was okay with and something python3 is not. Though I did export it all to a new folder where I thought maybe it was a path issue? Its importing local classes. I can post the full Label.py file. – Codejoy Jan 12 '22 at 18:48
  • "I did export it all to a new folder" sounds relevant. If you got rid of a *package* named `Label` in the process, the code may have been assuming `Label` was a module containing a class named `FloatLabel`. – chepner Jan 12 '22 at 18:49
  • Well the code is 'local' (sorry not sure what the word is). So its something we wrote in house that lives in the same folder as the .py trying to use. These are all the ones I am having to edit though is the using of that local code. Hence the fact it seems to not like from . import Label then later say Label.FloatLabel , it doesn't find it. So my from . import is wrong? or how it is defined in Label.py (FloatLabel) again my work around was just to import FloatLabel too: from . import FloatLabel then use just that no Label.FloatLabel in the .py files trying to reference it. – Codejoy Jan 12 '22 at 18:55
  • Your `Label` **class** has no `FloatLabel()` method (as the error indicates). This would not have worked in Python 2 or 3 — something else is the issue. – martineau Jan 12 '22 at 19:03
  • Agreed it was only with a few of those imports that was using the . and locally written. I went ahead and just 'refactored it' to get it to compile and things are good now. – Codejoy Jan 12 '22 at 20:55

0 Answers0