Skip to content

Commit

Permalink
Add documentation and typing to code changes.
Browse files Browse the repository at this point in the history
* Ignore D106, this will be quite annoying with marshmallow's required
  Meta subclass definition.
  • Loading branch information
adithyabsk committed Apr 12, 2020
1 parent 9b6e880 commit a6cba78
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 25 deletions.
2 changes: 1 addition & 1 deletion pyrh/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""pyrh models and schemas"""
"""pyrh models and schemas."""

from .oauth import Challenge, OAuth
from .sessionmanager import SessionManager, dump_session, load_session
Expand Down
62 changes: 54 additions & 8 deletions pyrh/models/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Base Model."""

from types import SimpleNamespace
from typing import Any, Dict, Mapping, Tuple
from typing import Any, Dict, List, Mapping, Union

from marshmallow import INCLUDE, Schema, post_load

Expand All @@ -11,42 +11,88 @@
MAX_REPR_LEN = 50


def _process_dict_values(
value: Union[Dict[str, Any], List[Any]]
) -> Union["UnknownModel", List[Any]]:
"""Process a returned from a JSON response.
Args:
value: A dict or a list returned from a JSON response.
Returns:
Either an UnknownModel or a List of processed values.
"""
if isinstance(value, Mapping):
return UnknownModel(**value)
if isinstance(value, list):
return [_process_dict_values(v) for v in value]


class BaseModel(SimpleNamespace):
"""TODO."""
"""BaseModel that all models should inherit from.
Note:
If a passed parameter is a nested dictionary, then it is created with the
`UnknownModel` class. If it is a list, then it is created with
Args:
**kwargs: All passed parameters as converted to instance attributes.
"""

def __init__(self, **kwargs: Any) -> None:
kwargs = {
k: UnknownModel(**v) if isinstance(v, Mapping) else v
for k, v in kwargs.items()
}
kwargs = {k: _process_dict_values(v) for k, v in kwargs.items()}

self.__dict__.update(kwargs)

def __repr__(self) -> str:
"""Return a default repr of any Model.
Returns:
The string model parameters up to a `MAX_REPR_LEN`.
"""
repr_ = super().__repr__()
if len(repr_) > MAX_REPR_LEN:
return repr_[:MAX_REPR_LEN] + " ...)"
else:
return repr_

def __len__(self) -> int:
"""Return the length of the model.
Returns:
The number of attributes a given model has.
"""
return len(self.__dict__)


class UnknownModel(BaseModel):
"""TODO."""
"""A convenience class that inherits from `BaseModel`."""

pass


class BaseSchema(Schema):
"""TODO."""
"""The default schema for all models."""

__model__: Any = UnknownModel
"""Determines the object that is created when the load method is called."""

class Meta:
unknown = INCLUDE

@post_load
def make_object(self, data: JSON, **kwargs: Any) -> "__model__":
"""Build model for the given `__model__` class attribute.
Args:
data: The JSON diction to use to build the model.
**kwargs: Unused but required to match signature of `Schema.make_object`
Returns:
An instance of the `__model__` class.
"""
return self.__model__(**data)
36 changes: 35 additions & 1 deletion pyrh/models/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,28 @@


class Challenge(BaseModel):
"""The challenge response model."""

remaining_attempts = 0
"""Default `remaining_attempts` attribute if it is not set on instance."""

@property
def can_retry(self) -> bool:
"""TODO."""
"""Determine if the challenge can be retried.
Returns:
True if remaining_attempts is greater than zero and challenge is not \
expired, False otherwise.
"""
return self.remaining_attempts > 0 and (
datetime.now(tz=pytz.utc) < self.expires_at
)


class ChallengeSchema(BaseSchema):
"""The challenge response schema."""

__model__ = Challenge

id = fields.UUID()
Expand All @@ -36,20 +47,43 @@ class ChallengeSchema(BaseSchema):


class OAuth(BaseModel):
"""The OAuth response model."""

@property
def is_challenge(self) -> bool:
"""Determine whether the oauth response is a challenge.
Returns:
True response has the `challenge` key, False otherwise.
"""
return hasattr(self, "challenge")

@property
def is_mfa(self) -> bool:
"""Determine whether the oauth response is a mfa challenge.
Returns:
True response has the `mfa_required` key, False otherwise.
"""
return hasattr(self, "mfa_required")

@property
def is_valid(self) -> bool:
"""Determine whether the oauth response is a valid response.
Returns:
True if the response has both the `access_token` and `refresh_token` keys, \
False otherwise.
"""
return hasattr(self, "access_token") and hasattr(self, "refresh_token")


class OAuthSchema(BaseSchema):
"""The OAuth response schema."""

__model__ = OAuth

detail = fields.Str()
Expand Down
Loading

0 comments on commit a6cba78

Please sign in to comment.