0

I am trying to make a MVC framework with routing. The model itself is iterable. If I don't use a router class then it will give all the first keys from the model.

But when using the router class, I get the following error: TypeError: 'ABCMeta' object is not iterable

I have a simplified version of the source code:

Model

from abc import ABC, abstractmethod

class Model(ABC):
    @abstractmethod
    def __iter__(self):
        ...

    @abstractmethod
    def get(self, item):
        ...

    @property
    @abstractmethod
    def item_type(self, name):
        ...


class TestModel(Model):
    test_items = {
        "one": { "name": "1" },
        "two": { "name": "2" },
    }

    item_type = "test"

    def __iter__(self):
        yield from self.test_items
        

    def get(self, item):
        try:
            return self.test_items[item]
        except KeyError as key_err:
            print("Key cannot be found", key_err)

Controller

from abc import ABC, abstractmethod

class Controller(ABC):
    @abstractmethod
    def __iter__(self):
        ...

    @abstractmethod
    def show_items(self):
        ...

    @abstractmethod
    def show_item_information(self, item_name):
        ...

class TestController:
    def __init__(self, model, view):
        self.view = view
        self.model = model

    def show_items(self):
        items = list(self.model)
        item_type = self.model.item_type
        self.view.show_item_list(item_type, items)

    def show_item_information(self, item_name):
        try:
            item_info = self.model.get(item_name)
        except Exception:
            item_type = self.model.item_type
            self.view.item_not_found(item_type, item_name)
        else:
            item_type = self.model.item_type
            self.view.show_item_information(item_type, item_name, item_info)
 

Router

class Router:
    def __init__(self):
        self.routes = {}

    def register_controller(self, path, controller, model, view):
        self.routes[path] = controller(model, view)

    def resolve(self, path):
        callback = self.routes.get(path)
        if callback:
            return callback
        return self.default_controller

    def default_controller(self, *args, **kwargs):
        status = 'Controller not found'
        return status

Main

#!/usr/bin/env python3

from sys import argv

from lib.router import Router
from lib.model import TestModel
from lib.view import TestView
from lib.controller import TestController

def main():
    router = Router()
    router.register_controller('test', TestController, TestModel, TestView)

    path = argv[1]

    controller = router.resolve(path)
    controller.show_items()


if __name__ == '__main__':
    main()

What I expect is that with the routering that the model stays iterable.

I have tried to build in a check if the class is Iterable. But this isn't really a solution.

Typnix
  • 1
  • 1
  • You're never creating an instance of your `TestModel` class, so when you try to iterate, you're iterating over the class, not over an instance. You've been using `cls` in place of `self` for the name of the first argument to every method, which makes me think you may be having some confusion about class/instance differences. – Blckknght Aug 09 '23 at 21:31
  • But the class itself is iterable. If you would print the TestModel class without the router class, you will get the data because of __iter__ in the class. But I would think that I need to do something in the router class. – Typnix Aug 10 '23 at 14:33
  • The code you've shown doesn't do that. An `__iter__` method in a class only makes the *instance* of that class iterable. For the class object itself to be iterable, you need to add an `__iter__` method in a metaclass. Just changing the name `self` to `cls` doesn't do that. – Blckknght Aug 10 '23 at 18:36
  • But changing 'cls' to 'self' doesn't change the behavior. The problem remains the same. __Iter__ is still not being respected. – Typnix Aug 11 '23 at 21:31
  • Right, I think the `self` vs `cls` name issue is *a symptom* of a wider confusion about what behaviors belong to the class, and which to its instances. If you want to be able to do `for x in TestModel`, you need to write a metaclass, the `__iter__` method defined in `TestModel` itself won't do it. `TestModel.__iter__` gets called when you iterate on an *instance* of the class, not when you iterate on the class itself. – Blckknght Aug 11 '23 at 21:45

0 Answers0