Your foo method needs to be called on an instance of Demo, not by itself. In both versions of your code, calling foo without looking it up in something (probably an instance) is an error.
Perhaps you want:
def foo(self, key):
obj = Demo(self[key])
obj.foo(key)
This will work for as long as your data has nested dictionaries under the same key. It will fail when self[key] doesn't return a dictionary. Presumably you want to have a base case to handle that:
def foo(self, key):
value = self[key] # you might want a further base case to handle the key not existing at all!
if not isinstance(value, dict):
pass # if you have something to do for the base case, do it here
else: # recursive case
obj = Demo(value)
obj.foo(key)
Now, this class is a bit silly, it copies lots of stuff just so that you can have a method that runs on a dict. A much more sensable approach would get rid of the class and just use a recursive function with two arguments, and you'd have no need for creating obj in the recursive case:
# this foo is a top-level function, not part of a class!
def foo(data, key): # no self argument any more
value = data[key]
...
foo(value, key) # recursive call is simpler now
Demo.foo(bar, key), since you don't want a globalfoo, but the class attribute by that name.fooisn't a static method or a regular function.dictas an argument