<commit_before>"""Provides the code to load PRAW's configuration file `praw.ini`."""
import os
import sys

from six.moves import configparser

from .exceptions import ClientException


class _NotSet(object):
    def __bool__(self):
        return False

    __nonzero__ = __bool__

    def __str__(self):
        return 'NotSet'


class Config(object):
    """A class containing the configuration for a reddit site."""

    CONFIG = None
    CONFIG_NOT_SET = _NotSet()  # Represents a config value that is not set.

    @staticmethod
    def _config_boolean(item):
        if isinstance(item, bool):
            return item
        return item.lower() in {'1', 'yes', 'true', 'on'}

    @classmethod
    def _load_config(cls):
        """Attempt to load settings from various praw.ini files."""
        config = configparser.RawConfigParser()
        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):
        """Return the short url or raise a ClientException when 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, **settings):
        """Initialize a Config instance."""
        if Config.CONFIG is None:
            self._load_config()

        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.refresh_token = self.redirect_uri = None
        self.password = self.user_agent = self.username = None

        self._initialize_attributes()

    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('praw_{}'.format(key))
        ini_value = self._fetch_default(key)  # Needed to remove from custom

        # Environment variables have higher priority than praw.ini settings<commit_msg>Handle observed thread safety issue in config loading<commit_after>"""Provides the code to load PRAW's configuration file `praw.ini`."""
from threading import Lock
import os
import sys

from six.moves import configparser

from .exceptions import ClientException


class _NotSet(object):
    def __bool__(self):
        return False

    __nonzero__ = __bool__

    def __str__(self):
        return 'NotSet'


class Config(object):
    """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()

    @staticmethod
    def _config_boolean(item):
        if isinstance(item, bool):
            return item
        return item.lower() in {'1', 'yes', 'true', 'on'}

    @classmethod
    def _load_config(cls):
        """Attempt to load settings from various praw.ini files."""
        config = configparser.RawConfigParser()
        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):
        """Return the short url or raise a ClientException when 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, **settings):
        """Initialize a Config instance."""
        with Config.LOCK:
            if Config.CONFIG is None:
                self._load_config()

        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.refresh_token = self.redirect_uri = None
        self.password = self.user_agent = self.username = None

        self._initialize_attributes()

    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('praw_{}'.format(key))
        ini_value = self._fetch_default(key)  # Needed to remove from custom

        # Environment variables have higher priority than praw.ini settings
{'fraction_non_alphanumeric': 0.09805227234892626, 'fraction_numerical': 0.000665889795238888, 'mean_word_length': 2.555029585798817, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5646422', 'n_tokens_mistral': 1745, 'n_tokens_neox': 1714, 'n_words': 492}