Skip to content

Commit

Permalink
Set up initial CLI command and configuration
Browse files Browse the repository at this point in the history
Why these changes are being introduced:
CLI command structure and config need to be updated from
the template repo defaults.

How this addresses that need:
* Adds expected command options and updates configuration as needed.
* Updates tests to reflect all changes.
* Updates README with descriptions for all required environemnt
variables

Relevant ticket(s):
* https://mitlibraries.atlassian.net/browse/IN-732
  • Loading branch information
adamshire123 committed Feb 24, 2023
1 parent 66ac571 commit e7da799
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 75 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ coveralls: test

### Code quality and safety commands ###

lint: bandit black mypy pylama safety ## Run linting, code quality, and safety checks
lint: bandit black mypy pylama pydocstyle safety ## Run linting, code quality, and safety checks

bandit:
pipenv run bandit -r sapinvoices
Expand All @@ -43,6 +43,9 @@ mypy:
pylama:
pipenv run pylama --options setup.cfg

pydocstyle:
pipenv run pydocstyle sapinvoices

safety:
pipenv check
pipenv verify
Expand Down
4 changes: 3 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ name = "pypi"
[packages]
click = "*"
sentry-sdk = "*"
freezegun = "*"

[dev-packages]
bandit = "*"
Expand All @@ -15,9 +16,10 @@ coveralls = "*"
mypy = "*"
pylama = {extras = ["all"], version = "*"}
pytest = "*"
pydocstyle = "*"

[requires]
python_version = "3.11"

[scripts]
sapinvoices = "python -c \"from sapinvoices.cli import main; main()\""
sap = "python -c \"from sapinvoices.cli import sap; sap()\""
82 changes: 53 additions & 29 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ sets trasmitted invoice to paid in Alma
- To run the app: `pipenv run sapinvoices --help`

## Required ENV

- `SENTRY_DSN` = If set to a valid Sentry DSN, enables Sentry exception monitoring. This is not needed for local development.
- `WORKSPACE` = Set to `dev` for local development, this will be set to `stage` and `prod` in those environments by Terraform.
- `ALMA_API_URL` = Base URL for making Alma API calls
- `ALMA_API_READ_WRITE_KEY` = API key for making Alma API calls
- `SAP_DROPBOX_CONNECTION` = JSON formatted connection information for accessing SAP dropbox
- `SAP_REPLY_TO_EMAIL`
- `SAP_REPLY_TO_EMAIL` = reply-to email on emails to SAP recipient email lists
- `SAP_FINAL_RECIPIENT_EMAIL` = moira list to recieves final run emails
- `SAP_REVIEW_RECIPIENT_EMAIL` = moira list to recieve review run emails
- `SES_SEND_FROM_EMAIL`
- `SSM_PATH`
- `LOG_LEVEL`
- `SES_SEND_FROM_EMAIL` = email address that SES sends from
- `SSM_PATH` = the path to ssm variables
- `WORKSPACE` = Set to `dev` for local development, this will be set to `stage` and `prod` in those environments by Terraform.

## Optional ENV
- `LOG_LEVEL` = Optional, set to a valid Python logging level (e.g. DEBUG, case-insensitive) if desired. Can also be passed as an option directly to the ccslips command. Defaults to INFO if not set or passed to the command.
- `SENTRY_DSN` = If set to a valid Sentry DSN, enables Sentry exception monitoring. This is not needed for local development.
86 changes: 73 additions & 13 deletions sapinvoices/cli.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,88 @@
import datetime
import logging
from datetime import timedelta
from time import perf_counter
from typing import Optional

import click

from sapinvoices.config import configure_logger, configure_sentry
from sapinvoices.config import configure_logger, configure_sentry, load_config_values

logger = logging.getLogger(__name__)


@click.command()
@click.group()
@click.pass_context
def sap(ctx: click.Context) -> None:
ctx.ensure_object(dict)
ctx.obj["today"] = datetime.datetime.today()


@sap.command()
@click.pass_context
def create_sandbox_data(ctx: click.Context) -> None:
"""Create sample data in the Alma sandbox instance.
In order to run successfully, the sandbox Acquisitions read/write API key must be
set in config (in .env if running locally, or in SSM if on stage). This command
will not run in the production environment, and should never be run with production
config values.
"""
logger.info("Creating sample data in Alma sandbox for today: %s", ctx.obj["today"])
click.echo("sap data gets created in the sandbox...")


@sap.command()
@click.option(
"--final-run",
is_flag=True,
help="Flag to indicate this is a final run and should include all steps of the "
"process. Default if this flag is not passed is to do a review run, which only "
"creates and sends summary and report files for review by stakeholders. Note: some "
"steps of a final run will not be completed unless the '--real-run' flag is also "
"passed, however that will write data to external systems and should thus be used "
"with caution. See '--real-run' option documentation for details.",
)
@click.option(
"--real-run",
is_flag=True,
help="USE WITH CAUTION. If '--real-run' flag is passed, files will be emailed "
"to stakeholders and, if the '--final-run' flag is also passed, invoices will be "
"sent to SAP and marked as paid in Alma. If this flag is not passed, this command "
"defaults to a dry run, in which files will not be emailed or sent to SAP, instead "
"their contents will be logged for review, and invoices will also not be marked as "
"paid in Alma.",
)
@click.option(
"-v", "--verbose", is_flag=True, help="Pass to log at debug level instead of info"
"-l",
"--log-level",
envvar="LOG_LEVEL",
help="Case-insensitive Python log level to use, e.g. debug or warning. Defaults to "
"INFO if not provided or found in ENV.",
)
def main(verbose: bool) -> None:
start_time = perf_counter()
@click.pass_context
def process_invoices(
ctx: click.Context, final_run: bool, real_run: bool, log_level: Optional[str]
) -> None:
"""Process invoices for payment via SAP.
Retrieves "Waiting to be sent" invoices from Alma, extracts and formats data
needed for submission to SAP for payment. If not a final run, creates and sends
formatted review reports to Acquisitions staff. If a final run, creates and sends
formatted cover sheets and summary reports to Acquisitions staff, submits data and
control files to SAP, and marks invoices as paid in Alma after submission to SAP.
"""
config_values = load_config_values()
log_level = log_level or "INFO"
root_logger = logging.getLogger()
logger.info(configure_logger(root_logger, verbose))
logger.info(configure_logger(root_logger, log_level))
logger.info(configure_sentry())
logger.info("Running process")

# Do things here!

elapsed_time = perf_counter() - start_time
logger.info(
"Total time to complete process: %s", str(timedelta(seconds=elapsed_time))
"alma-sapinvoices config settings loaded for environment: %s",
config_values["WORKSPACE"],
)

logger.info("Starting SAP invoices process with options: \n")
logger.info("Date: %s \n", ctx.obj["today"])
logger.info("Final run: %s \n", final_run)
logger.info("Real run: %s", real_run)
click.echo("invoice processing happens...")
35 changes: 31 additions & 4 deletions sapinvoices/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,36 @@

import sentry_sdk

EXPECTED_ENVIRONMENT_VARIABLES = [
"ALMA_API_URL",
"ALMA_API_READ_WRITE_KEY",
"SAP_DROPBOX_CONNECTION",
"SAP_REPLY_TO_EMAIL",
"SAP_FINAL_RECIPIENT_EMAIL",
"SAP_REVIEW_RECIPIENT_EMAIL",
"SES_SEND_FROM_EMAIL",
"SSM_PATH",
"WORKSPACE",
]

def configure_logger(logger: logging.Logger, verbose: bool) -> str:
if verbose:

def configure_logger(logger: logging.Logger, log_level_string: str) -> str:
if log_level_string.upper() not in logging.getLevelNamesMapping():
raise ValueError(f"'{log_level_string}' is not a valid Python logging level")
log_level = logging.getLevelName(log_level_string.upper())
if log_level < 20:
logging.basicConfig(
format="%(asctime)s %(levelname)s %(name)s.%(funcName)s() line %(lineno)d: "
"%(message)s"
)
logger.setLevel(logging.DEBUG)
logger.setLevel(log_level)
for handler in logging.root.handlers:
handler.addFilter(logging.Filter("sapinvoices"))
else:
logging.basicConfig(
format="%(asctime)s %(levelname)s %(name)s.%(funcName)s(): %(message)s"
)
logger.setLevel(logging.INFO)
logger.setLevel(log_level)
return (
f"Logger '{logger.name}' configured with level="
f"{logging.getLevelName(logger.getEffectiveLevel())}"
Expand All @@ -31,3 +46,15 @@ def configure_sentry() -> str:
sentry_sdk.init(sentry_dsn, environment=env)
return f"Sentry DSN found, exceptions will be sent to Sentry with env={env}"
return "No Sentry DSN found, exceptions will not be sent to Sentry"


def load_config_values() -> dict:
settings = {
variable: os.environ[variable] for variable in EXPECTED_ENVIRONMENT_VARIABLES
}
if "prod" in settings["SSM_PATH"] and settings["WORKSPACE"] != "prod":
raise RuntimeError(
"Production SSM_PATH may ONLY be used in the production "
"environment. Check your env variables and try again."
)
return settings
7 changes: 4 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ ignore_missing_imports = True

[pylama]
ignore = C0114,C0116,D100,D103,W0012
linters = eradicate,isort,mccabe,pycodestyle,pydocstyle,pyflakes,pylint
linters = eradicate,isort,mccabe,pycodestyle,pyflakes,pylint
max_line_length = 90

[pylama:isort]
profile = black

[pylama:pydocstyle]
convention = pep257
[pydocstyle]
ignore = D100,D103,D213


[tool:pytest]
log_level = DEBUG
Expand Down
14 changes: 13 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@

@pytest.fixture(autouse=True)
def test_env():
os.environ = {"SENTRY_DSN": None, "WORKSPACE": "test"}
os.environ = {
"ALMA_API_URL": "test",
"ALMA_API_READ_WRITE_KEY": "test",
"LOG_LEVEL": "DEBUG",
"SAP_DROPBOX_CONNECTION": "test",
"SAP_REPLY_TO_EMAIL": "test",
"SAP_FINAL_RECIPIENT_EMAIL": "test",
"SAP_REVIEW_RECIPIENT_EMAIL": "test",
"SENTRY_DSN": None,
"SES_SEND_FROM_EMAIL": "test",
"SSM_PATH": "test",
"WORKSPACE": "test",
}
yield


Expand Down
Loading

0 comments on commit e7da799

Please sign in to comment.