Skip to content

Commit

Permalink
Expose APIClientOptions in library interface and documentation (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
psrok1 authored Feb 23, 2022
1 parent 6249cd9 commit c11bc85
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 21 deletions.
3 changes: 3 additions & 0 deletions docs/mwdblib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ Core interface (MWDB)

.. autoclass:: APIClient
:members:

.. autoclass:: APIClientOptions
:members:
4 changes: 2 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Sphinx==3.0.3
Sphinx==4.4.0
sphinx-autodoc-annotation==1.0.post1
sphinx-autodoc-typehints==1.10.3
sphinx-rtd-theme==0.5.0
sphinx-rtd-theme==1.0.0
3 changes: 2 additions & 1 deletion mwdblib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .__version__ import __version__
from .api import APIClient
from .api import APIClient, APIClientOptions
from .blob import MWDBBlob
from .comment import MWDBComment
from .config import MWDBConfig
Expand All @@ -11,6 +11,7 @@
__all__ = [
"MWDB",
"APIClient",
"APIClientOptions",
"MWDBFile",
"MWDBObject",
"MWDBConfig",
Expand Down
3 changes: 2 additions & 1 deletion mwdblib/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .api import APIClient
from .options import APIClientOptions

__all__ = ["APIClient"]
__all__ = ["APIClient", "APIClientOptions"]
53 changes: 52 additions & 1 deletion mwdblib/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,53 @@ def __init__(self, _auth_token: Optional[str] = None, **api_options: Any) -> Non

@property
def server_metadata(self) -> dict:
"""
Information about MWDB Core server from ``/api/server`` endpoint.
"""
if self._server_metadata is None:
self._server_metadata = self.get("server", noauth=True)
return self._server_metadata

@property
def server_version(self) -> str:
"""
MWDB Core server version
"""
return str(self.server_metadata["server_version"])

@property
def logged_user(self) -> Optional[str]:
"""
Username of logged-in user or the owner of used API key.
Returns None if no credentials are provided
"""
return self.auth_token.username if self.auth_token else None

def supports_version(self, required_version: str) -> bool:
"""
Checks if server version is higher or equal than provided.
.. versionadded:: 4.1.0
"""
return parse_version(self.server_version) >= parse_version(required_version)

def set_auth_token(self, auth_key: str) -> None:
self.auth_token = JWTAuthToken(auth_key)
self.session.headers.update(
{"Authorization": f"Bearer {self.auth_token.value}"}
)

def login(self, username: str, password: str) -> None:
"""
Performs authentication using provided credentials
.. note:
It's not recommended to use this method directly.
Use :py:meth:`MWDB.login` instead.
:param username: Account username
:param password: Account password
"""
token = self.post(
"auth/login", json={"login": username, "password": password}, noauth=True
)["token"]
Expand All @@ -119,11 +147,23 @@ def login(self, username: str, password: str) -> None:
self.options.password = password

def set_api_key(self, api_key: str) -> None:
"""
Sets API key to be used for authorization
.. note:
It's not recommended to use this method directly.
Pass ``api_key`` argument to ``MWDB`` constructor.
:param api_key: API key to set
"""
self.set_auth_token(api_key)
# Store credentials in API options
self.options.api_key = api_key

def logout(self) -> None:
"""
Removes authorization token from APIClient instance
"""
self.auth_token = None
self.session.headers.pop("Authorization")

Expand All @@ -150,10 +190,21 @@ def request(
**kwargs: Any,
) -> Any:
"""
Sends request to MWDB API.
Sends request to MWDB API. This method can be used for accessing features that
are not directly supported by mwdblib library.
Other keyword arguments are the same as in requests library.
.. seealso::
Use functions specific for HTTP methods instead of passing
``method`` argument on your own:
- :py:meth:`APIClient.get`
- :py:meth:`APIClient.post`
- :py:meth:`APIClient.put`
- :py:meth:`APIClient.delete`
:param method: HTTP method
:param url: Relative url of API endpoint
:param noauth: |
Expand Down
24 changes: 20 additions & 4 deletions mwdblib/api/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ def __set__(self, instance: Any, value: Any) -> None:


class APIClientOptions:
"""
Options bag that contains configuration for APIClient.
Field values are loaded using the following precedence:
- built-in defaults accessible via class properties e.g.
``APIClientOptions.api_url``
- values from ``~/.mwdb`` configuration file
- values passed as an arguments to the ``APIClientOptions`` constructor
Configuration may depend on ``api_url`` value, so remember to set it if you want to
talk with specific MWDB Core instance.
"""

# Register fields and defaults
api_url = OptionsField("https://mwdb.cert.pl/api/")
api_key = OptionsField(value_type=str)
Expand Down Expand Up @@ -146,8 +160,9 @@ def __init__(

def clear_stored_credentials(self, config_writeback: bool = True) -> None:
"""
Clears stored credentials in configuration for current user
Used by `mwdb logout` CLI command
Clears stored credentials in configuration for current user.
Used by ``mwdb logout`` CLI command.
"""
if not self.username:
return
Expand Down Expand Up @@ -176,8 +191,9 @@ def clear_stored_credentials(self, config_writeback: bool = True) -> None:

def store_credentials(self) -> None:
"""
Stores current credentials in configuration for current user
Used by `mwdb login` CLI command
Stores current credentials in configuration for current user.
Used by ``mwdb login`` CLI command.
"""
if not self.username or (not self.api_key and not self.password):
return
Expand Down
30 changes: 18 additions & 12 deletions mwdblib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,31 @@ class MWDB:
"""
Main object used for communication with MWDB REST API
:param api_url: MWDB API URL that ends with '/api/'.
:param api_url: MWDB API URL (that ends with '/api/').
:param api_key: MWDB API key
:param username: MWDB account username
:param password: MWDB account password
:param verify_ssl: Verify SSL certificate correctness (default: True)
:param obey_ratelimiter: If false, HTTP 429 errors will cause an exception
:param obey_ratelimiter: If ``False``, HTTP 429 errors will cause an exception
like all other error codes.
If true (default), library will transparently handle them by sleeping
for a specified duration.
:param retry_on_downtime: If true, requests will be automatically retried
after 10 seconds on HTTP 502/504 and ConnectionError.
If ``True``, library will transparently handle them by sleeping
for a specified duration. Default is ``True``.
:param retry_on_downtime: If ``True``, requests will be automatically retried
after ``downtime_timeout`` seconds on HTTP 502/504 and ConnectionError.
Default is ``False``.
:param max_downtime_retries: Number of retries caused by temporary downtime
:param downtime_timeout: How long we need to wait between retries (in seconds)
:param downtime_timeout: How long we need to wait between retries (in seconds).
Default is 10.
:param retry_idempotent: Retry idempotent POST requests (default).
The only thing that is really non-idempotent in current API is
:meth:`MWDBObject.add_comment`, so it's not a big deal. You can turn it off
if possible doubled comments are problematic in your MWDB instance.
:param use_keyring: If true (default), APIClient uses keyring to fetch
Default is ``True``.
:param use_keyring: If ``True``, APIClient uses keyring to fetch
stored credentials. If not, they're fetched from plaintext configuration.
:param emit_warnings: If true (default), warnings are emitted by APIClient
Default is ``True``.
:param emit_warnings: If ``True``, warnings are emitted by APIClient.
Default is ``True``.
:param config_path: Path to the configuration file (default is `~/.mwdb`).
If None, configuration file will not be used by APIClient
:param api: Custom :class:`APIClient` to be used for communication with MWDB
Expand Down Expand Up @@ -83,7 +88,6 @@ class MWDB:
mwdb.login("example", "<password>")
file = mwdb.query_file("3629344675705286607dd0f680c66c19f7e310a1")
"""

def __init__(self, api: Optional[APIClient] = None, **api_options: Any):
Expand All @@ -105,9 +109,11 @@ def login(
"""
Performs user authentication using provided username and password.
If credentials are not set, asks interactively for credentials.
.. warning::
Keep in mind that password-authenticated sessions are short lived,
Keep in mind that password-authenticated sessions are short-lived,
so password needs to be stored in :class:`APIClient` object.
Consider generating a new API key in your MWDB profile.
Expand All @@ -125,7 +131,7 @@ def login(
:py:meth:`MWDB.login` no longer warns about password-authenticated sessions
or credentials that are already set up.
:param username: User name
:param username: Username
:type username: str
:param password: Password
:type password: str
Expand Down

0 comments on commit c11bc85

Please sign in to comment.