/
config.py
169 lines (142 loc) · 5.48 KB
/
config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""Provides the code to load Async PRAW's configuration file `praw.ini`."""
import configparser
import os
import sys
from threading import Lock
from typing import Optional
from warnings import warn
from .exceptions import ClientException
class _NotSet:
def __bool__(self):
return False
__nonzero__ = __bool__
def __str__(self):
return "NotSet"
class Config:
"""A class containing the configuration for a reddit site."""
CONFIG = None
CONFIG_NOT_SET = _NotSet() # Represents a config value that is not set.
LOCK = Lock()
INTERPOLATION_LEVEL = {
"basic": configparser.BasicInterpolation,
"extended": configparser.ExtendedInterpolation,
}
@staticmethod
def _config_boolean(item):
if isinstance(item, bool):
return item
return item.lower() in {"1", "yes", "true", "on"}
@classmethod
def _load_config(cls, config_interpolation: Optional[str] = None):
"""Attempt to load settings from various praw.ini files."""
if config_interpolation is not None:
interpolator_class = cls.INTERPOLATION_LEVEL[config_interpolation]()
else:
interpolator_class = None
config = configparser.ConfigParser(interpolation=interpolator_class)
module_dir = os.path.dirname(sys.modules[__name__].__file__)
if "APPDATA" in os.environ: # Windows
os_config_path = os.environ["APPDATA"]
elif "XDG_CONFIG_HOME" in os.environ: # Modern Linux
os_config_path = os.environ["XDG_CONFIG_HOME"]
elif "HOME" in os.environ: # Legacy Linux
os_config_path = os.path.join(os.environ["HOME"], ".config")
else:
os_config_path = None
locations = [os.path.join(module_dir, "praw.ini"), "praw.ini"]
if os_config_path is not None:
locations.insert(1, os.path.join(os_config_path, "praw.ini"))
config.read(locations)
cls.CONFIG = config
@property
def short_url(self) -> str:
"""Return the short url.
:raises: :class:`.ClientException` if it is not set.
"""
if self._short_url is self.CONFIG_NOT_SET:
raise ClientException("No short domain specified.")
return self._short_url
def __init__(
self,
site_name: str,
config_interpolation: Optional[str] = None,
**settings: str,
):
"""Initialize a Config instance."""
with Config.LOCK:
if Config.CONFIG is None:
self._load_config(config_interpolation)
self._settings = settings
self.custom = dict(Config.CONFIG.items(site_name), **settings)
self.client_id = self.client_secret = self.oauth_url = None
self.reddit_url = self.redirect_uri = None
self.password = self.user_agent = self.username = None
self._initialize_attributes()
self._do_not_use_refresh_token = self._fetch_or_not_set("refresh_token")
if self._do_not_use_refresh_token != self.CONFIG_NOT_SET:
warn(
"The ``refresh_token`` configuration setting is deprecated and will be"
" removed in Async PRAW 8. Please use ``token_manager`` to manage your"
" refresh tokens.",
category=DeprecationWarning,
stacklevel=2,
)
def _fetch(self, key):
value = self.custom[key]
del self.custom[key]
return value
def _fetch_default(self, key, default=None):
if key not in self.custom:
return default
return self._fetch(key)
def _fetch_or_not_set(self, key):
if key in self._settings: # Passed in values have the highest priority
return self._fetch(key)
env_value = os.getenv(f"praw_{key}")
ini_value = self._fetch_default(key) # Needed to remove from custom
# Environment variables have higher priority than praw.ini settings
return env_value or ini_value or self.CONFIG_NOT_SET
def _initialize_attributes(self):
self._short_url = self._fetch_default("short_url") or self.CONFIG_NOT_SET
self.check_for_updates = self._config_boolean(
self._fetch_or_not_set("check_for_updates")
)
self.kinds = {
x: self._fetch(f"{x}_kind")
for x in [
"comment",
"message",
"redditor",
"submission",
"subreddit",
"trophy",
]
}
for attribute in (
"client_id",
"client_secret",
"redirect_uri",
"password",
"user_agent",
"username",
):
setattr(self, attribute, self._fetch_or_not_set(attribute))
for required_attribute in (
"oauth_url",
"ratelimit_seconds",
"reddit_url",
"timeout",
):
setattr(self, required_attribute, self._fetch(required_attribute))
for attribute, conversion in {
"ratelimit_seconds": int,
"timeout": int,
}.items():
try:
setattr(self, attribute, conversion(getattr(self, attribute)))
except ValueError:
raise ValueError(
f"An incorrect config type was given for option {attribute}. The"
f" expected type is {conversion.__name__}, but the given value is"
f" {getattr(self, attribute)}."
)