2

I try to override the__getattribute__ special method in a dataclass in order to get a formatted version of an attribute.

@dataclass
class _BaseRouteRegistry:
    VERSION = '1'
    USERS = 'users'

    def __getattribute__(self, item):
        route = object.__getattribute__(self, item)
        return f"/api/{route}/v{self.VERSION}"

BaseRouteRegistry = _BaseRouteRegistry()

print(BaseRouteRegistry.USERS)

But I got a RecursionError. Where I expected to get in the output: /api/users/v1

What I don't get if I directly return the object.__getattribute__(self, item) like so :

@dataclass
class _BaseRouteRegistry:
    VERSION = '1'
    USERS = 'users'

    def __getattribute__(self, item):
        return object.__getattribute__(self, item)

BaseRouteRegistry = _BaseRouteRegistry()

print(BaseRouteRegistry.USERS)

But then I just got users in output (of course not formatted since I removed the format string expression)

I saw many other answers on the RecusrionError like those ones :

How do I implement __getattribute__ without an infinite recursion error?

python __getattribute__ RecursionError when returning class variable attribute

In those questions above, they suggest to use the unbounded object.__getattribute__(self, item).

I do understand why, and I tried to use it.

But in my example, since I need to return a formatted version of the attribute, I first need to retrieve it and only then return the formatted version.

How to achieve this ?

jossefaz
  • 3,312
  • 4
  • 17
  • 40

2 Answers2

4

You got an infinite recursion when you try to access self.VERSION here f"/api/{route}/v{self.VERSION}" because self.VERSION also invokes __getattribute__ method

So, you should handle VERSION attribute separately

from dataclasses import dataclass


@dataclass
class _BaseRouteRegistry:
    VERSION = '1'
    USERS = 'users'

    def __getattribute__(self, item):
        if item == "VERSION":
            return object.__getattribute__(self, item)

        route = object.__getattribute__(self, item)
        return f"/api/{route}/v{self.VERSION}"

BaseRouteRegistry = _BaseRouteRegistry()

print(BaseRouteRegistry.USERS)
> /api/users/v1
Yevhen Bondar
  • 4,357
  • 1
  • 11
  • 31
  • Of course ! How didn't I see this ! Thanks a lot. By the way I will simply put the Version out of the class and it works. Thanks – jossefaz Dec 26 '21 at 17:19
1

Eugenij's answer explains the issue with your code and how to fix it. However, if the goal is to control access to a specific attribute USERS, then you don't need to override __getattribute__() which affects access to all attributes. Instead, you can make USERS a property:

class _BaseRouteRegistry:
    VERSION = '1'
    _USERS = 'users'
    
    @property
    def USERS(self):
        return f"/api/{self._USERS}/v{self.VERSION}"

BaseRouteRegistry = _BaseRouteRegistry()

print(BaseRouteRegistry.USERS) 
bb1
  • 7,174
  • 2
  • 8
  • 23
  • Upvote for the answer but this is exactly what I expect (affect the access of each attributes so @Eugenji 's answer is correct for me). Thanks – jossefaz Dec 26 '21 at 17:18