• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

praw-dev / praw / 3768928224

pending completion
3768928224

Pull #1923

github

GitHub
Merge 33b610e6e into ffe9f71d6
Pull Request #1923: Improve tests, clean up test code, and sort test functions/classes

4109 of 4109 relevant lines covered (100.0%)

4.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/praw/config.py
1
"""Provides the code to load PRAW's configuration file ``praw.ini``."""
2
import configparser
4✔
3
import os
4✔
4
import sys
4✔
5
from threading import Lock
4✔
6
from typing import Optional
4✔
7

8
from .exceptions import ClientException
4✔
9

10

11
class _NotSet:
4✔
12
    def __bool__(self):
4✔
13
        return False
4✔
14

15
    __nonzero__ = __bool__
4✔
16

17
    def __str__(self):
4✔
18
        return "NotSet"
4✔
19

20

21
class Config:
4✔
22
    """A class containing the configuration for a Reddit site."""
23

24
    CONFIG = None
4✔
25
    CONFIG_NOT_SET = _NotSet()  # Represents a config value that is not set.
4✔
26
    LOCK = Lock()
4✔
27
    INTERPOLATION_LEVEL = {
4✔
28
        "basic": configparser.BasicInterpolation,
29
        "extended": configparser.ExtendedInterpolation,
30
    }
31

32
    @staticmethod
4✔
33
    def _config_boolean(item):
3✔
34
        if isinstance(item, bool):
4✔
35
            return item
4✔
36
        return item.lower() in {"1", "yes", "true", "on"}
4✔
37

38
    @classmethod
4✔
39
    def _load_config(cls, *, config_interpolation: Optional[str] = None):
4✔
40
        """Attempt to load settings from various praw.ini files."""
41
        if config_interpolation is not None:
4✔
42
            interpolator_class = cls.INTERPOLATION_LEVEL[config_interpolation]()
4✔
43
        else:
44
            interpolator_class = None
4✔
45
        config = configparser.ConfigParser(interpolation=interpolator_class)
4✔
46
        module_dir = os.path.dirname(sys.modules[__name__].__file__)
4✔
47
        if "APPDATA" in os.environ:  # Windows
4✔
48
            os_config_path = os.environ["APPDATA"]
4✔
49
        elif "XDG_CONFIG_HOME" in os.environ:  # Modern Linux
4✔
50
            os_config_path = os.environ["XDG_CONFIG_HOME"]
4✔
51
        elif "HOME" in os.environ:  # Legacy Linux
4✔
52
            os_config_path = os.path.join(os.environ["HOME"], ".config")
4✔
53
        else:
54
            os_config_path = None
4✔
55
        locations = [os.path.join(module_dir, "praw.ini"), "praw.ini"]
4✔
56
        if os_config_path is not None:
4✔
57
            locations.insert(1, os.path.join(os_config_path, "praw.ini"))
4✔
58
        config.read(locations)
4✔
59
        cls.CONFIG = config
4✔
60

61
    @property
4✔
62
    def short_url(self) -> str:
4✔
63
        """Return the short url.
64

65
        :raises: :class:`.ClientException` if it is not set.
66

67
        """
68
        if self._short_url is self.CONFIG_NOT_SET:
4✔
69
            raise ClientException("No short domain specified.")
4✔
70
        return self._short_url
4✔
71

72
    def __init__(
4✔
73
        self,
74
        site_name: str,
75
        config_interpolation: Optional[str] = None,
76
        **settings: str,
77
    ):
78
        """Initialize a :class:`.Config` instance."""
79
        with Config.LOCK:
4✔
80
            if Config.CONFIG is None:
4✔
81
                self._load_config(config_interpolation=config_interpolation)
4✔
82

83
        self._settings = settings
4✔
84
        self.custom = dict(Config.CONFIG.items(site_name), **settings)
4✔
85

86
        self.client_id = self.client_secret = self.oauth_url = None
4✔
87
        self.reddit_url = self.refresh_token = self.redirect_uri = None
4✔
88
        self.password = self.user_agent = self.username = None
4✔
89

90
        self._initialize_attributes()
4✔
91

92
    def _fetch(self, key):
4✔
93
        value = self.custom[key]
4✔
94
        del self.custom[key]
4✔
95
        return value
4✔
96

97
    def _fetch_default(self, key, *, default=None):
4✔
98
        if key not in self.custom:
4✔
99
            return default
4✔
100
        return self._fetch(key)
4✔
101

102
    def _fetch_or_not_set(self, key):
4✔
103
        if key in self._settings:  # Passed in values have the highest priority
4✔
104
            return self._fetch(key)
4✔
105

106
        env_value = os.getenv(f"praw_{key}")
4✔
107
        ini_value = self._fetch_default(key)  # Needed to remove from custom
4✔
108

109
        # Environment variables have higher priority than praw.ini settings
110
        return env_value or ini_value or self.CONFIG_NOT_SET
4✔
111

112
    def _initialize_attributes(self):
4✔
113
        self._short_url = self._fetch_default("short_url") or self.CONFIG_NOT_SET
4✔
114
        self.check_for_async = self._config_boolean(
4✔
115
            self._fetch_default("check_for_async", default=True)
116
        )
117
        self.check_for_updates = self._config_boolean(
4✔
118
            self._fetch_or_not_set("check_for_updates")
119
        )
120
        self.warn_comment_sort = self._config_boolean(
4✔
121
            self._fetch_default("warn_comment_sort", default=True)
122
        )
123
        self.kinds = {
4✔
124
            x: self._fetch(f"{x}_kind")
125
            for x in [
126
                "comment",
127
                "message",
128
                "redditor",
129
                "submission",
130
                "subreddit",
131
                "trophy",
132
            ]
133
        }
134

135
        for attribute in (
4✔
136
            "client_id",
137
            "client_secret",
138
            "redirect_uri",
139
            "refresh_token",
140
            "password",
141
            "user_agent",
142
            "username",
143
        ):
144
            setattr(self, attribute, self._fetch_or_not_set(attribute))
4✔
145

146
        for required_attribute in (
4✔
147
            "oauth_url",
148
            "ratelimit_seconds",
149
            "reddit_url",
150
            "timeout",
151
        ):
152
            setattr(self, required_attribute, self._fetch(required_attribute))
4✔
153

154
        for attribute, conversion in {
4✔
155
            "ratelimit_seconds": int,
156
            "timeout": int,
157
        }.items():
158
            try:
4✔
159
                setattr(self, attribute, conversion(getattr(self, attribute)))
4✔
160
            except ValueError:
4✔
161
                raise ValueError(
4✔
162
                    f"An incorrect config type was given for option {attribute}. The"
163
                    f" expected type is {conversion.__name__}, but the given value is"
164
                    f" {getattr(self, attribute)}."
165
                )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc