Skip to content

Commit

Permalink
config: improved configuration loading system
Browse files Browse the repository at this point in the history
  • Loading branch information
fotini-pan authored and slint committed Aug 5, 2019
1 parent faa6ef5 commit 7b0f01d
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 128 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Expand Up @@ -17,3 +17,5 @@ recursive-exclude * __pycache__
recursive-exclude * *.py[co]

recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif

recursive-include autobot *.ini
64 changes: 56 additions & 8 deletions autobot/api.py
Expand Up @@ -8,29 +8,77 @@

"""Autobot API."""

from autobot.config import Config
import yaml

from autobot.config_loader import Config
from autobot.github import GitHubAPI


class BotAPI:
"""Generates report."""

def __init__(self, config: Config):
@classmethod
def __init__(cls, config: Config):
"""Bot initialization."""
self.config = config
self.report = GitHubAPI(self.config)._report(self.config._load_repositories())
cls.config = config.config
cls.report = GitHubAPI(
cls.config["AUTOBOT_OWNER"], cls.config["AUTOBOT_GH_TOKEN"]
).report(cls.load_repositories())

def generate_report(self, maintainer: str) -> dict:
@classmethod
def generate_report(cls, maintainer: str) -> dict:
"""Generates a report for a maintainer."""
res = self.report[0]["repos"]
res = cls.report[0]["repos"]
print(res)
print(
"---------------------------------------------------------------------------------"
)
return res

def send_report(self, maintainer: str, format: str):
@classmethod
def send_report(cls, maintainer: str, format: str):
"""Send the report to a maintainer (on Gitter or via email)."""
res = self.generate_report(maintainer)
res = cls.generate_report(maintainer)
if format == "markdown":
""" TO DO """

@classmethod
def load_repositories_yml(cls):
"""Load repository.yml file."""
return yaml.load(open(cls.config["AUTOBOT_INFO_PATH"]))["orgs"][
cls.config["AUTOBOT_OWNER"]
]

@classmethod
def load_repositories(cls):
"""Load repository.yml file into dictionary with repositories as keys."""
info = cls.load_repositories_yml()
res = {
repo: info["repositories"][repo]["maintainers"]
for repo in info["repositories"].keys()
}
if cls.config["AUTOBOT_REPOS"]:
res = {repo: res[repo] for repo in cls.config["AUTOBOT_REPOS"]}
if cls.config["AUTOBOT_MAINTAINERS"]:
res = {
repo: list(
filter(lambda m: m in res[repo], cls.config["AUTOBOT_MAINTAINERS"])
)
for repo in res.keys()
}
return {r: m for r, m in res.items() if m}

@classmethod
def invert_list_dict(cls, d):
"""Invert dictionary `d`."""
keys = list(set([k for val in d.values() for k in val]))
res = dict.fromkeys(keys)
for k in res.keys():
res[k] = [val for (val, l) in d.items() if k in l]
return res

@classmethod
def load_maintainers(cls):
"""Load repository.yml file into dictionary with maintainers as keys."""
info = cls.load_repositories()
return cls.invert_list_dict(info)
16 changes: 10 additions & 6 deletions autobot/cli.py
Expand Up @@ -16,7 +16,7 @@
from github3 import login, repository

from autobot.api import BotAPI
from autobot.config import Config
from autobot.config_loader import Config


@click.group(context_settings=dict(help_option_names=["-h", "--help"]))
Expand Down Expand Up @@ -45,12 +45,14 @@ def report():
def show(owner, repo, maintainer, format):
"""Autobot report show cli."""
conf = Config(
owner=owner, repos=[r for r in repo], maintainers=[m for m in maintainer]
AUTOBOT_OWNER=owner,
AUTOBOT_REPOS=[r for r in repo],
AUTOBOT_MAINTAINERS=[m for m in maintainer],
)
bot = BotAPI(conf)
res = bot.report
with open("results.yml", "w") as outfile:
yaml.dump(res, outfile, default_flow_style=False)
# with open("results.yml", "w") as outfile:
# yaml.dump(res, outfile, default_flow_style=False)
if format == "json":
print(res)
elif format == "yaml":
Expand All @@ -74,10 +76,12 @@ def show(owner, repo, maintainer, format):
def send(owner, repo, maintainer, via):
"""Autobot report send cli."""
conf = Config(
owner=owner, repos=[r for r in repo], maintainers=[m for m in maintainer]
AUTOBOT_OWNER=owner,
AUTOBOT_REPOS=[r for r in repo],
AUTOBOT_MAINTAINERS=[m for m in maintainer],
)
bot = BotAPI(conf)
for m in conf._load_maintainers().keys():
for m in bot.load_maintainers().keys():
if via == "gitter":
bot.send_report(m, "markdown")
return 0
Expand Down
3 changes: 3 additions & 0 deletions autobot/config.ini
@@ -0,0 +1,3 @@
[AUTOBOT]
AUTOBOT_INFO_PATH = autobot/opensource/repositories.yml
AUTOBOT_OWNER = inveniosoftware
135 changes: 80 additions & 55 deletions autobot/config.py
Expand Up @@ -6,59 +6,84 @@
# Autobot is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Configuration options for Autobot."""

import os

import yaml
from dotenv import load_dotenv


class Config:
"""Loads config from environment and files."""

def __init__(self, **kwargs):
"""Config initialization."""
load_dotenv()
self.owner = kwargs.get("owner", os.getenv("OWNER"))
self.repos = kwargs.get("repos", [])
self.maintainers = kwargs.get("maintainers", [])
self.GITHUB_TOKEN = kwargs.get("github_token", os.getenv("GH_TOKEN"))
self.INFO_PATH = kwargs.get(
"info_path", os.getenv(self.owner.upper() + "_INFO")
)
self.GITTER_TOKEN = "CHANGE_ME"
self.MAIL_SETTINGS = {...}

def _load_repositories_yml(self):
"""Load repository.yml file."""
return yaml.load(open(self.INFO_PATH))["orgs"][self.owner]

def _load_repositories(self):
"""Load repository.yml file into dictionary with repositories as keys."""
info = self._load_repositories_yml()
res = {
repo: info["repositories"][repo]["maintainers"]
for repo in info["repositories"].keys()
"""The details of the configuration options for Autobot (deafult values given)."""

AUTOBOT_GH_TOKEN = "CHANGE ME"
"""The github token used for making requests to the github API.
It **must** be a none empty string corresponding to a valid github token.
"""

AUTOBOT_GITTER_TOKEN = "CHANGE ME"
"""The gitter token used for sending the generated report via gitter API.
It **must** be a none empty string corresponding to a valid gitter token.
"""

AUTOBOT_INFO_PATH = "autobot/opensource/repositories.yml"
"""The path to the **yaml** file that contains the information about the organization we provide the service for.
It **must** be a none empty string corresponding to the direcotry of a yaml file of the following structure:
.. code-block:: python
info = {
"orgs": {
"valid organization name here": {
"repositories": {
"valid repository name here": {
"maintainers": # maintainers list,
"some attribute": # corresponding value,
"some other attribute": # corresponding value,
# and so on ...
},
"valid repository name here": {
"maintainers": # maintainers list,
"some attribute": # corresponding value,
"some other attribute": # corresponding value,
# and so on ...
},
# and so on ...
}
},
"valid organization name here": {
"repositories": {
"valid repository name here": {
"maintainers": # maintainer list,
"some attribute": # corresponding value,
"some other attribute": # corresponding value,
# and so on ...
},
"valid repository name here": {
"maintainers": # maintainer list,
"some attribute": # corresponding value,
"some other attribute": # corresponding value,
# and so on ...
},
# and so on ...
}
},
# and so on ...
}
if self.repos:
res = {repo: res[repo] for repo in self.repos}
if self.maintainers:
res = {
repo: list(filter(lambda m: m in res[repo], self.maintainers))
for repo in res.keys()
}
return {r: m for r, m in res.items() if m}

def _invert_list_dict(self, d):
"""Invert dictionary `d`."""
keys = list(set([k for val in d.values() for k in val]))
res = dict.fromkeys(keys)
for k in res.keys():
res[k] = [val for (val, l) in d.items() if k in l]
return res

def _load_maintainers(self):
"""Load repository.yml file into dictionary with maintainers as keys."""
info = self._load_repositories()
return self._invert_list_dict(info)
}
"""

# AUTOBOT_MAIL_SETTINGS={}

AUTOBOT_MAINTAINERS = []
"""A string list of the github users that are listed as maintainers (see ``AUTOBOT_INFO_PATH``) of the repositories of interest (see ``AUTOBOT_REPOS``).
Every item of the list **must** be a none empty string corresponding to a valid github user.
"""

AUTOBOT_OWNER = "inveniosoftware"
"""The name of the organization we provide the service for.
It **must** be a none empty string corresponding to a valid github user.
"""

AUTOBOT_REPOS = []
"""A string list of the github repositories to generate reports for.
Every item of the list **must** be a none empty string corresponding to a valid github repository owned by ``AUTOBOT_OWNER``.
"""
66 changes: 66 additions & 0 deletions autobot/config_loader.py
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2019 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Performs project's configuration loading."""

import os
from configparser import ConfigParser

from dotenv.main import dotenv_values

import autobot.config as config


class Config:
"""Interact with configuration variables."""

dotenv_path = os.path.join(os.path.abspath(os.path.join(__file__, "..")), ".env")

ini_parser = ConfigParser()
ini_path = os.path.join(os.path.abspath(os.path.join(__file__, "..")), "config.ini")

config = {}

@classmethod
def __init__(cls, **kwargs):
"""Initialize configuration."""
cls.load_config(**kwargs)

@classmethod
def load_config(cls, **kwargs):
"""Get autobot configuration values."""
cls.config = cls.env_config()
cls.ini_parser.optionxform = str
cls.ini_parser.read(cls.ini_path)
cls.config.update(cls.ini_config())
cls.config.update(kwargs)
py_config = cls.py_config()
defaults = {
key: py_config[key] for key in py_config if key not in cls.config.keys()
}
cls.config.update(defaults)
cls.config = {key: cls.config[key] for key in py_config}

@classmethod
def env_config(cls):
"""Get autobot configuration values from .env file."""
return dotenv_values(cls.dotenv_path)

@classmethod
def ini_config(cls):
"""Get autobot configuration values from config.ini file."""
return cls.ini_parser["AUTOBOT"]

@classmethod
def py_config(cls):
"""Get autobot configuration values from config.py file."""
res = {}
for k in dir(config):
if k.startswith("AUTOBOT_"):
res.setdefault(k, getattr(config, k))
return res

0 comments on commit 7b0f01d

Please sign in to comment.