pythonpython-3.x

Data access object not finding self attributes


I have a data access object in Python/SQLite3 defined as:

import sqlite3


  class Dao:

    VEHICLE_TABLE_SCHEMA = """
                    CREATE TABLE IF NOT EXISTS vehicles (
                        vehicle_key integer NOT NULL PRIMARY KEY AUTOINCREMENT,
                        vehicle_description TEXT NOT NULL
                    )
                """

    VEHICLE_INSERT_SQL = "INSERT INTO vehicles (vehicle_description) VALUES (?)"

    VEHICLE_LIST_SQL = "SELECT * FROM vehicles"

    MILAGE_TABLE_SCHEMA = """
                  CREATE TABLE IF NOT EXISTS milage (
                    milage_date DATE NOT NULL PRIMARY KEY,
                    vehicle_id INTEGER NOT NULL,
                    milage integer NOT NULL
                  )
                """

    MILAGE_INSERT_SQL = "INSERT INTO milage ( milage_date , vehicle_id , milage ) VALUES (? , ? , ? )"

    def __init__(self, dbname = "vehicles.sqlite3" ):
        # self.conn = sqlite3.connect(dbname, autocommit=True)
        self.create_table_if_missing( self.MILAGE_TABLE_SCHEMA )
        self.create_table_if_missing( self.VEHICLE_TABLE_SCHEMA )
        self.dbname = dbname


    def create_table_if_missing(self , sql ):
    #Creates the 'vehicle' table in the database if it doesn't exist."""
        with sqlite3.connect(self.dbname) as conn:
            cursor = conn.cursor()
            cursor.execute( sql )


    def add_vehicle(self , description ):

        with sqlite3.connect(self.dbname) as conn:
            cursor = conn.cursor()
            cursor.execute( self.VEHICLE_INSERT_SQL , ( description , ) )


    def add_milage(self , milage_date , vehicle_id , milage ):

        with sqlite3.connect(self.dbname) as conn:
            cursor = conn.cursor()
            cursor.execute( self.MILAGE_INSERT_SQL , ( milage_date , vehicle_id , milage ) )


    def list_vehicles(self):

        with sqlite3.connect(self.dbname) as conn:
            cursor = conn.cursor()
            cursor.execute( self.VEHICLE_LIST_SQL )
            return cursor.fetchall()

I try calling it from a class called MilageEntryFrame shown in part below:

import customtkinter as ctk
from dao import Dao

class MilageEntryFrame( ctk.CTkFrame ):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)

        self.frm_milage_entry = ctk.CTkFrame(self )
        self.frm_milage_entry.pack(fill="x", padx=5, pady=25)

        vehicle_records = Dao.list_vehicles( self )

I get an error stating:

  File "D:\PycharmProjects\VehicleMaint\MilageEntryFrame.py", line 12, in __init__
    vehicle_records = Dao.list_vehicles( self )
  File "D:\PycharmProjects\VehicleMaint\dao.py", line 57, in list_vehicles
    with sqlite3.connect(self.dbname) as conn:
                         ^^^^^^^^^^^
AttributeError: 'MilageEntryFrame' object has no attribute 'dbname'. Did you mean: '_name'?

The first thing to catch my eye is it saying MilageEntryFrame has no attribute 'dbname'. This is correct, it doesn't have it, nor should it need it as it is set in the DAO class, not in the MilageEntryFrame. I'm guessing it has something to do with my passing self from the MilageEntryFrame class to the DAO class. But, it complains if I don't pass it. Coincidentally, I asked ChatGPT to create a DAO class to do what I'm seeking and it's response was virtually identical to my code. I've converted my code to try using with rather than the try/catch I originally had and that ChatGPT had as well.


Solution

  • The issue is that you can't just substitute self between classes. self isn't something magical, it's just the naming convention for the parameter holding a reference to the object a member function/method is invoked on. In your case, Dao.list_vehicles expects self to be an instance of the Dao class, but you're explicitly passing it an instance of MilageEntryFrame.

    >>> class MyClass:
    ...     def some_method(self):
    ...             pass
    ...
    >>> type(MyClass.some_method)
    <class 'function'>
    >>> type(MyClass().some_method)
    <class 'method'>
    

    Effectively, you're circumventing the Python interpreter's implicit passing of self when you're calling list_vehicles on the class itself and not an instance of that class.

    You want something more like:

    import customtkinter as ctk
    from dao import Dao
    
    class MilageEntryFrame( ctk.CTkFrame ):
        def __init__(self, master, **kwargs):
            super().__init__(master, **kwargs)
    
            self.frm_milage_entry = ctk.CTkFrame(self )
            self.frm_milage_entry.pack(fill="x", padx=5, pady=25)
    
            vehicle_records = Dao().list_vehicles()  # Notice the added '()' and the removed `self`
    

    As @ticktalk points out in their comment on the post, there are also other issues with the order of initialization that you'll run into once you actually instantiate Dao, so this is the answer to the question as posted, but your script won't run until you fix also the other issue(s).