Skip to content

Commit

Permalink
Refactor Configuration into dataclass (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
barrust committed Jan 16, 2024
1 parent 5f4cfe2 commit 523a675
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 124 deletions.
6 changes: 4 additions & 2 deletions .pylintrc
Expand Up @@ -151,7 +151,9 @@ disable=raw-checker-failed,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead
use-symbolic-message-instead,
too-many-arguments,
protected-access,

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down Expand Up @@ -598,7 +600,7 @@ variable-naming-style=snake_case
[EXCEPTIONS]

# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException, builtins.Exception
overgeneral-exceptions=mediawiki.exceptions.BaseException,builtins.Exception


[LOGGING]
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
# MediaWiki Changelog

## Version 0.7.5

* Move configuration items to a configuration data class
* Will allow for the deprication of some top level properties in lieu of changing against the `Configuration` class

## Version 0.7.4

* Add typing support
Expand Down
5 changes: 3 additions & 2 deletions docs/source/conf.py
Expand Up @@ -15,6 +15,7 @@

import os
import sys
from typing import Dict, List

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Expand Down Expand Up @@ -88,7 +89,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
exclude_patterns: List[str] = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
Expand Down Expand Up @@ -224,7 +225,7 @@

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
latex_elements: Dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
Expand Down
3 changes: 2 additions & 1 deletion mediawiki/__init__.py
@@ -1,6 +1,7 @@
"""
mediawiki module initialization
"""
from mediawiki.configuraton import URL, VERSION
from mediawiki.exceptions import (
DisambiguationError,
HTTPTimeoutError,
Expand All @@ -12,7 +13,7 @@
PageError,
RedirectError,
)
from mediawiki.mediawiki import URL, VERSION, MediaWiki
from mediawiki.mediawiki import MediaWiki
from mediawiki.mediawikipage import MediaWikiPage

__author__ = "Tyler Barrus"
Expand Down
270 changes: 270 additions & 0 deletions mediawiki/configuraton.py
@@ -0,0 +1,270 @@
"""Configuration module"""
from dataclasses import asdict, dataclass, field
from datetime import datetime, timedelta
from typing import Dict, Optional, Union

URL: str = "https://github.com/barrust/mediawiki"
VERSION: str = "0.7.4"


@dataclass
class Configuration:
"""Configuration class"""

_lang: str = field(default="en", init=False, repr=False)
_api_url: str = field(default="https://en.wikipedia.org/w/api.php", init=False, repr=False)
_category_prefix: str = field(default="Category", init=False, repr=False)
_timeout: Optional[float] = field(default=15.0, init=False, repr=False)
_user_agent: str = field(default=f"python-mediawiki/VERSION-{VERSION}/({URL})/BOT", init=False, repr=False)
_proxies: Optional[Dict] = field(default=None, init=False, repr=False)
_verify_ssl: Union[bool, str] = field(default=True, init=False, repr=False)
_rate_limit: bool = field(default=False, init=False, repr=False)
_rate_limit_min_wait: timedelta = field(default=timedelta(milliseconds=50), init=False, repr=False)
_username: Optional[str] = field(default=None, init=False, repr=False)
_password: Optional[str] = field(default=None, init=False, repr=False)
_refresh_interval: Optional[int] = field(default=None, init=False, repr=False)
_use_cache: bool = field(default=True, init=False, repr=False)

# not in repr
_reset_session: bool = field(default=True, init=False, repr=False)
_clear_memoized: bool = field(default=False, init=False, repr=False)
_rate_limit_last_call: Optional[datetime] = field(default=None, init=False, repr=False)

def __init__(
self,
lang: Optional[str] = None,
api_url: Optional[str] = None,
category_prefix: Optional[str] = None,
timeout: Optional[float] = None,
user_agent: Optional[str] = None,
proxies: Optional[Dict] = None,
verify_ssl: Union[bool, str, None] = None,
rate_limit: bool = False,
rate_limit_wait: Optional[timedelta] = None,
username: Optional[str] = None,
password: Optional[str] = None,
refresh_interval: Optional[int] = None,
use_cache: bool = True,
):
if api_url:
self._api_url = api_url

if lang:
self.lang = lang

if category_prefix:
self.category_prefix = category_prefix

if user_agent:
self._user_agent = user_agent

if proxies:
self.proxies = proxies

if verify_ssl:
self.verify_ssl = verify_ssl

if rate_limit:
self.rate_limit = rate_limit

if rate_limit_wait:
self._rate_limit_min_wait = rate_limit_wait

if username:
self.username = username

if password:
self.password = password

if refresh_interval:
self.refresh_interval = refresh_interval

if use_cache:
self.use_cache = use_cache

if timeout:
self.timeout = timeout

def __repr__(self):
"""repr"""
keys = [
x.replace("_", "", 1)
for x in sorted(asdict(self).keys())
if x not in ["_rate_limit_last_call", "_clear_memoized", "_reset_session"]
]
full = [f"{x}={self.__getattribute__(x)}" for x in keys]
return f"Configuration({', '.join(full)})"

@property
def lang(self) -> str:
"""str: The API URL language, if possible this will update the API URL
Note:
Use correct language titles with the updated API URL
Note:
Some API URLs do not encode language; unable to update if this is the case"""
return self._lang

@lang.setter
def lang(self, language: str):
"""Set the language to use; attempts to change the API URL"""
if self._lang == language.lower():
return
url = self._api_url
tmp = url.replace(f"/{self._lang}.", f"/{language.lower()}.")

self.api_url = tmp
self._lang = language.lower()
self._clear_memoized = True

@property
def api_url(self) -> str:
"""str: API URL of the MediaWiki site
Note:
Not settable; See :py:func:`mediawiki.MediaWiki.set_api_url`"""
return self._api_url

@api_url.setter
def api_url(self, api_url: str):
self._lang = self.lang.lower()
self._api_url = api_url.format(lang=self._lang)

# reset session
self._reset_session = True

@property
def category_prefix(self) -> str:
"""str: The category prefix to use when using category based functions
Note:
Use the correct category name for the language selected"""
return self._category_prefix

@category_prefix.setter
def category_prefix(self, category_prefix: str):
"""Set the category prefix correctly"""
self._category_prefix = category_prefix[:-1] if category_prefix[-1:] == ":" else category_prefix

@property
def user_agent(self) -> str:
"""str: User agent string
Note:
If using in as part of another project, this should be changed"""
return self._user_agent

@user_agent.setter
def user_agent(self, user_agent: str):
"""Set the new user agent string
Note:
Will need to re-log into the MediaWiki if user agent string is changed"""
self._user_agent = user_agent

@property
def proxies(self) -> Optional[Dict]:
"""dict: Turn on, off, or set proxy use with the Requests library"""
return self._proxies

@proxies.setter
def proxies(self, proxies: Optional[Dict]):
"""Turn on, off, or set proxy use through the Requests library"""
self._proxies = proxies if isinstance(proxies, dict) else None

# reset session
self._reset_session = True

@property
def verify_ssl(self) -> Union[bool, str]:
"""bool | str: Verify SSL when using requests or path to cert file"""
return self._verify_ssl

@verify_ssl.setter
def verify_ssl(self, verify_ssl: Union[bool, str, None]):
"""Set request verify SSL parameter; defaults to True if issue"""
self._verify_ssl = verify_ssl if isinstance(verify_ssl, (bool, str)) else True

# reset session
self._reset_session = True

@property
def rate_limit(self) -> bool:
"""bool: Turn on or off Rate Limiting"""
return self._rate_limit

@rate_limit.setter
def rate_limit(self, rate_limit: bool):
"""Turn on or off rate limiting"""
self._rate_limit = bool(rate_limit)
self._rate_limit_last_call = None
self._clear_memoized = True

@property
def rate_limit_min_wait(self) -> timedelta:
"""timedelta: Time to wait between calls
Note:
Only used if rate_limit is **True**"""
return self._rate_limit_min_wait

@rate_limit_min_wait.setter
def rate_limit_min_wait(self, min_wait: timedelta):
"""Set minimum wait to use for rate limiting"""
self._rate_limit_min_wait = min_wait
self._rate_limit_last_call = None

@property
def username(self) -> Optional[str]:
"""str | None: Username to use to log into the mediawiki site"""
return self._username

@username.setter
def username(self, username: Optional[str]):
"""set the username, if needed, to log into the mediawiki site"""
self._username = username

@property
def password(self) -> Optional[str]:
"""str | None: Password to use to log into the mediawiki site"""
return self._password

@password.setter
def password(self, password: Optional[str]):
"""set the password, if needed, to log into the mediawiki site"""
self._password = password

@property
def refresh_interval(self) -> Optional[int]:
"""int | None: The interval at which the memoize cache is to be refresh"""
return self._refresh_interval

@refresh_interval.setter
def refresh_interval(self, refresh_interval: Optional[int]):
"Set the new cache refresh interval" ""
self._refresh_interval = (
refresh_interval if isinstance(refresh_interval, int) and refresh_interval > 0 else None
)

@property
def use_cache(self) -> bool:
"""bool: Whether caching should be used; on (**True**) or off (**False**)"""
return self._use_cache

@use_cache.setter
def use_cache(self, use_cache: bool):
"""toggle using the cache or not"""
self._use_cache = bool(use_cache)

@property
def timeout(self) -> Optional[float]:
"""float: Response timeout for API requests
Note:
Use **None** for no response timeout"""
return self._timeout

@timeout.setter
def timeout(self, timeout: Optional[float]):
"""Set request timeout in seconds (or fractions of a second)"""
self._timeout = None if timeout is None else float(timeout)

0 comments on commit 523a675

Please sign in to comment.