Skip to content

BaseSettings.setdefault does nothing #5811

Closed
@Prometheus3375

Description

@Prometheus3375

Description

Calling setdefault method of class BaseSettings does nothing.

Steps to Reproduce

from scrapy.settings import BaseSettings
settings = BaseSettings()
stored = settings.setdefault('key', 'value')
print(stored)  # prints None
print(settings.copy_to_dict())  # prints empty dictionary

Expected behavior:
settings.setdefault(key, default) must work as described in MutableMapping interface: set default to settings[key] and return default if key is not present, otherwise return settings[key].

Actual behavior:
settings.setdefault(key, default) does nothing regardless of holding key or not.

Reproduces how often: 100%

Versions

Scrapy : 2.7.1
lxml : 4.8.0.0
libxml2 : 2.9.12
cssselect : 1.1.0
parsel : 1.6.0
w3lib : 1.22.0
Twisted : 22.4.0
Python : 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)]
pyOpenSSL : 22.0.0 (OpenSSL 3.0.3 3 May 2022)
cryptography : 37.0.2
Platform : Windows-10-10.0.19044-SP0

Additional context

BaseSettings explicitly inherits from MutableMapping and does not redefine setdefault method. Thus, it uses base implementation:

def setdefault(self, key, default=None):
    'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
    try:
        return self[key]
    except KeyError:
        self[key] = default
    return default

Base implementation refers to self[key] which is in fact self.__getitem__[key]. BaseSettings has own __getitem__ implementation:

def __getitem__(self, opt_name):
    if opt_name not in self:
        return None
    return self.attributes[opt_name].value

And here is the root of the problem: when passed key is not present, __getitem__ returns None, and setdefault follows.

Solution
Implement own setdefault method. An example with matching signature:

def setdefault(self, opt_name, default=None):
    if opt_name not in self:
        self.set(opt_name, default)
        return default
    return self.attributes[opt_name].value

priority='project' argument can be added although this changes signature.

Other way is to inherit from Mapping instead of MutableMapping if this method and other base methods are redundant.

Current workaround
Convert BaseSettings object to a dictionary and only then use setdefault.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions