Skip to content

Commit

Permalink
modify base api for runtime initialization
Browse files Browse the repository at this point in the history
  • Loading branch information
ZenithClown committed Oct 18, 2021
1 parent 93dca86 commit d7a8aaa
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 3 deletions.
169 changes: 169 additions & 0 deletions VisualCrossing/_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# -*- encoding: utf-8 -*-

import json
import time
import platform
import warnings

from time import ctime
from uuid import uuid4
from sys import modules
from shutil import copy
from os.path import join
from pathlib import Path

from . import (
__name__,
__version__,
__homepath__,
)
from .errors import * # noqa: F403
from ._config import config

class base(object):
"""A base class that wraps essential utilities for :class:`API`
:class:`API` is a python wrapper, to fetch API data from VisualCrossing,
which can be used to fetch historic as well as forecasted weather data.
"""

def __init__(self, **kwargs):
status, response = config()
if status == 100:
# use defaults
response = self.__args_default__()

for k, v in response.items():
setattr(self, k, v)


def __args_default__(self) -> dict:
"""Defines a Dictionary of Default Values for Keyword Arguments (or Attributes)"""

return {
"unitGroup" : "metric",
"contentType" : "csv",
"aggregateHours" : 24
}


def __get_args_default__(self, args : str):
"""Get the Default Value associated with a Keyword Argument"""

return self.__args_default__().get(args, None) # None if key not available


@property
def __optional_args__(self):
"""Get List of all the Optional Keyword Arguments Accepted by the API"""

return self.__args_default__().keys()


def generate_config(
self,
defaultSettings : bool = True,
fileName : str = "config.json",
overwrite : bool = False,
keepBackup : bool = True,
**kwargs
) -> bool:
"""Generate configuration file at `__homepath__` when executed
The configuration file can be generated with default settings as defined at
:func:`__args_default__` else, user is requested to pass all necessary settings
in a correct format (as required by API) to the function, setting `key` as the
attribute name, and `value` as the desired value. Users are also advised not to
save the `API_KEY` in the configuration file (for security purpose), and to use
:func:`_generate_key` to save the key file in an encrypted format.
:param defaultSettings: Should you wish to save the configuration file with
the default settings. If set to `False` then user is
requested to pass all necessary attributes (`key`) and
their values. Defaults to `True`.
:param fileName: Output file name (with extension - `json`). Defaults to
`config.json`.
:param overwrite: Overwrite existing configuration file, if exists (same filename).
Defaults to `False`.
:param keepBackup: If same file name exists, then setting the parameter to `True` will
create a backup of the file with the following format
`<original-name>.<UUID>.json` where `UUID` is a randomly generated
7-charecters long name. Defaults to `True`.
Accepts n-Keyword Arguments, which are all default settings that can be used to initialize
the API.
"""

outfile = join(__homepath__, fileName)

# get attributes to write to file
if defaultSettings:
attrs = self.__args_default__()
else:
attrs = kwargs # get all attribute from kwargs

# re-format attrs to a defined type, with meta-informations
attrs = {
"__header__" : {
"program" : __name__,
"version" : __version__,
"homepath" : __homepath__
},

"platform" : {
"platform" : platform.platform(),
"architecture" : platform.machine(),
"version" : platform.version(),
"system" : platform.system(),
"processor" : platform.processor(),
"uname" : platform.uname()
},

"attributes" : attrs,
"timestamp" : ctime()
}

# lambda to write to json file
# where `kv` is the key-value pair, which is typically `attrs`
# defined/reformatted in the above section
def write_json(kv : dict, file : str):
with open(file, "w") as f:
json.dump(kv, f, sort_keys = False, indent = 4, default = str)

if Path(outfile).is_file(): # file exists
warnings.warn(f"{outfile} already exists.", FileExists)
if keepBackup:
try:
name, extension = fileName.split(".")
except ValueError as err:
name = fileName.split(".")[0]
extension = "json"
warnings.warn(f"{fileName} is not of proper type. Setting name as: {name}", ImproperFileName)

new_file = ".".join([name, str(uuid4())[:7], extension])

# copy to a new file
print(f"Old configuration file is available at {new_file}")
try:
# python 3.8+
copy(outfile, join(__homepath__, new_file))
except TypeError:
# python <= 3.7
# https://stackoverflow.com/a/33626207/6623589
copy(str(outfile), str(join(__homepath__, new_file)))
else:
warnings.warn(f"{outfile} will be overwritten without a backup.", FileExists)

if overwrite:
write_json(attrs, outfile) # write to file
else:
raise ValueError(f"{outfile} already exists, and `overwrite` is set to `False`")

else:
# same file does not exists, other parameters are not required for validation
write_json(attrs, outfile) # write to file

return True
53 changes: 53 additions & 0 deletions VisualCrossing/_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# -*- encoding: utf-8 -*-

from json import load

# set configuration option
def config(file : str = None) -> dict:
"""Read a defined configuration file from home-path,
and set it to be used module wide."""

if file:
file = join(__homepath__, file)
if not Path(file).is_file():
raise FileNotFoundError(f"File {file} is not available.")
else:
status = 200 # use from configuration file

# file exists, read as a json file
params = load(open(file, "r")) # read as dictionary

# check if the formatting is correct
if "attributes" not in params.keys():
from .errors import WrongConfigFile
raise WrongConfigFile("Configuration file is wrong, check documentation.")
else:
# additionally, check other defined keys (as in generate file)
# is present, which ensures data integrity
optional = []
for k in ["__header__", "platform", "timestamp"]:
if k not in params.keys():
optional.append(k)

if optional:
import warnings
from .errors import VerificationWarning

warnings.warn(f"Config file missing {optional}", VerificationWarning)
params = params["attributes"]

else:
status = 100 # continue
params = None # use defualts

return status, params

# # set attribute to default class
# for k, v in params.items():
# globals()[k] = v

# # always initialize api with default values
# # once initialized, all the attributes are now
# # available as module level attrbute, thus the same
# # can be referenced as `VisualCrossing.unitGroup`
# config()
5 changes: 3 additions & 2 deletions VisualCrossing/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from datetime import datetime as dt
from requests.exceptions import SSLError

from ._api import base
from .errors import * # noqa: F403

class _base(object):
Expand All @@ -28,7 +29,7 @@ def __valid_keys__(self):
return ["key", "unitGroup", "contentType"]


class API(_base):
class API(base):
"""A basic API for Visual-Crossing Weather Data
:param date: Date for which weather data is required. Pass the date in `YYYY-MM-DD` format,
Expand Down Expand Up @@ -63,7 +64,7 @@ def __init__(

# define keyword arguments
self.endDate = kwargs.get("endDate", None)
self.unitGroup = kwargs.get("unitGroup", "metric")
# self.unitGroup = kwargs.get("unitGroup", "metric")
self.contentType = kwargs.get("contentType", "csv")
self.aggregateHours = kwargs.get("aggregateHours", 24)

Expand Down
11 changes: 10 additions & 1 deletion VisualCrossing/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ class WrongDateFormat(Exception):


class VerificationWarning(Warning):
"""Warning is raised when fetching data with flag `verify = False`"""
"""Warning is raised when fetching data with flag `verify = False` or config file not verified"""


class NoDataFetched(Exception):
"""Exception is raised when data is not fetched, i.e. received `status_code != 200`"""

class ImproperFileName(Warning):
"""Warning is raised when a file name is received which is not of format `name.extension`"""

class FileExists(Warning):
"""Warning is raised when a given file name already exists, and needs to be modified"""

class WrongConfigFile(Exception):
"""Error is raised when the given configuration file does not have a key named `attributes`"""

0 comments on commit d7a8aaa

Please sign in to comment.