Skip to content

Commit

Permalink
ENSY-99-repo-setup
Browse files Browse the repository at this point in the history
* Add Dockerfile
* Add Makefile
* Add Pipfile
* Add github workflow and commit template
* Add tests for lambda functions
* Add lambda functions
* Add setup.cfg to manage linting

add .python-version and update .gitignore

clean up tests

update Makefile

update doc string and explicitly initialize a list

fix linting errors

update buildsearchstring

fix problems from review
* update doc string
* add doc string and type hint
* update import order
* change some variables to named arguments in tests

changes from code review
* add docstring and type hinting to lambda_handler
* add type hinting to buildSearchString
*  lambda_handler handles requests without query string params
* update test for lambda_handler without query string params
  • Loading branch information
adamshire123 committed Jul 20, 2022
1 parent d572379 commit f198e12
Show file tree
Hide file tree
Showing 12 changed files with 1,012 additions and 1 deletion.
28 changes: 28 additions & 0 deletions .github/pull-request-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#### Helpful background context
Describe any additional context beyond what the PR accomplishes if it is likely
to be useful to a reviewer.

Delete this section if it isn't applicable to the PR.

#### How can a reviewer manually see the effects of these changes?
Explain how to see the proposed changes in the application if possible.

Delete this section if it isn't applicable to the PR.

#### What are the relevant tickets?
Include links to Jira Software and/or Jira Service Management tickets here.

#### Developer
- [ ] All new ENV is documented in README
- [ ] Stakeholder approval has been confirmed (or is not needed)

#### Code Reviewer
- [ ] The commit message is clear and follows our guidelines
(not just this pull request message)
- [ ] There are appropriate tests covering any new functionality
- [ ] The documentation has been updated or is unnecessary
- [ ] The changes have been verified
- [ ] New dependencies are appropriate or there were no changes

#### Includes new or updated dependencies?
YES | NO
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: CI
on: push
jobs:
test:
uses: mitlibraries/.github/.github/workflows/python-shared-test.yml@main
lint:
uses: mitlibraries/.github/.github/workflows/python-shared-lint.yml@main
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ profile_default/
ipython_config.py

# pyenv
.python-version
#.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9.12
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM public.ecr.aws/lambda/python:3.9

# Copy function code
COPY . ${LAMBDA_TASK_ROOT}/


# Default handler. See README for how to override to a different handler.
CMD [ "wcd2reshare.lambda_handler" ]
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
### Dependency commands ###
install: ## Install dependencies
pipenv install --dev

update: install ## Update all Python dependencies
pipenv clean
pipenv update --dev
pipenv requirements

### Test commands ###
test: ## Run tests and print a coverage report
pipenv run coverage run --include=wcd2reshare.py -m pytest
pipenv run coverage report -m

coveralls: test
pipenv run coverage lcov -o ./coverage/lcov.info

### Lint commands ###
lint: bandit black flake8 isort mypy ## Lint the repo

bandit:
pipenv run bandit -r wcd2reshare.py

black:
pipenv run black --check --diff .

flake8:
pipenv run flake8 .

isort:
pipenv run isort . --diff

mypy:
pipenv run mypy .


22 changes: 22 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]
bandit = "*"
black = "*"
coverage = "*"
flake8 = "*"
isort = "*"
moto = "*"
mypy = "*"
pytest = "*"
requests-mock = "*"
types-requests = "*"
requests = "*"

[requires]
python_version = "3.9"
661 changes: 661 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# These requirements were autogenerated by pipenv
# To regenerate from the project's Pipfile, run:
#
# pipenv lock --requirements
#

-i https://pypi.org/simple

8 changes: 8 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[flake8]
max-line-length = 90
extend-ignore = E203

[isort]
profile = black

[mypy]
165 changes: 165 additions & 0 deletions test_wcd2reshare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import pytest

import wcd2reshare


@pytest.fixture
def baseURL():
return "https://borrowdirect.reshare.indexdata.com/Search/Results?"


def test_isbn():
params = {"rft.isbn": "978-3-16-148410-0"}
assert wcd2reshare.query_formatter(params) == "type=ISN&lookfor=978-3-16-148410-0"


def test_title_no_author():
params = {"rft.title": "salad days"}
assert wcd2reshare.query_formatter(params) == "type=title&lookfor=salad+days"


def test_title_with_author():
params = {"rft.title": "salad days", "rft.aulast": "ranch"}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=salad+days&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_btitle_no_author():
params = {"rft.btitle": "salad days"}
assert wcd2reshare.query_formatter(params) == "type=title&lookfor=salad+days"


def test_btitle_with_author():
params = {"rft.btitle": "salad days", "rft.aulast": "ranch"}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=salad+days&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_ctitle_no_author():
params = {"rft.ctitle": "salad days"}
assert wcd2reshare.query_formatter(params) == "type=title&lookfor=salad+days"


def test_ctitle_with_author():
params = {"rft.ctitle": "salad days", "rft.aulast": "ranch"}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=salad+days&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_jtitle_no_author():
params = {"rft.jtitle": "salad days"}
assert wcd2reshare.query_formatter(params) == "type=title&lookfor=salad+days"


def test_jtitle_with_author():
params = {"rft.jtitle": "salad days", "rft.aulast": "ranch"}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=salad+days&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_no_rft_fields_of_interest():
params = {"rft.popcorn": "sure"}
assert wcd2reshare.query_formatter(params) == ""


def test_title_priority():
params = {
"rft.btitle": "btitle",
"rft.ctitle": "ctitle",
"rft.jtitle": "jtitle",
"rft.aulast": "ranch",
"rft.title": "title",
}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=title&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_btitle_priority():
params = {
"rft.ctitle": "ctitle",
"rft.jtitle": "jtitle",
"rft.aulast": "ranch",
"rft.btitle": "btitle",
}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=btitle&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_ctitle_priority():
params = {"rft.jtitle": "jtitle", "rft.aulast": "ranch", "rft.ctitle": "ctitle"}
assert wcd2reshare.query_formatter(params) == (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=ctitle&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)


def test_fields_of_no_interest_ignored():
params = {"rft.popcorn": "sure", "rft.isbn": "978-3-16-148410-0"}
assert wcd2reshare.query_formatter(params) == "type=ISN&lookfor=978-3-16-148410-0"


def test_lambda_handler_with_query(baseURL):
event = {"queryStringParameters": {"rft.isbn": "978-3-16-148410-0"}}
queryString = "type=ISN&lookfor=978-3-16-148410-0"
response = wcd2reshare.lambda_handler(event, context={})
assert response["statusCode"] == 307
assert response["headers"]["Location"] == baseURL + queryString


def test_lambda_handler_with_multiple_query(baseURL):
event = {
"queryStringParameters": {"rft.title": "salad days", "rft.aulast": "ranch"}
}
queryString = (
"join=AND&type0%5B%5D=title&lookfor0%5B%5D=salad+days&"
"type0%5B%5D=author&lookfor0%5B%5D=ranch"
)
response = wcd2reshare.lambda_handler(event, context={})
assert response["statusCode"] == 307
assert response["headers"]["Location"] == baseURL + queryString


def test_lambda_handler_without_query(baseURL):
response = wcd2reshare.lambda_handler(event={}, context={})
assert response["statusCode"] == 307
assert response["headers"]["Location"] == baseURL


def test_lambda_handler_with_garbage_query(baseURL):
response = wcd2reshare.lambda_handler(
event={"queryStringParameters": {"door": "knob"}}, context={}
)
assert response["statusCode"] == 307
assert response["headers"]["Location"] == baseURL


def test_buildSearchString_no_author():
title = "salad days"
assert wcd2reshare.buildSearchString(title, aulast=None) == [
("type", "title"),
("lookfor", title),
]


def test_buildSearchString_with_author():
title = "salad days"
aulast = "ranch"
assert wcd2reshare.buildSearchString(title, aulast) == [
("join", "AND"),
("type0[]", "title"),
("lookfor0[]", title),
("type0[]", "author"),
("lookfor0[]", aulast),
]
66 changes: 66 additions & 0 deletions wcd2reshare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import urllib.parse


def query_formatter(args: dict) -> str:
"""Transforms a dictionary of OpenURL request params to a string of search
params formatted for BorrowDirect / ReShare vuFind UI.
Logic is:
if isbn
use isbn and nothing else
else if any of the 4 titles exist:
use the title that exists in this preferential order
title, btitle, ctitle, jtitle
nested condition of if aulast include that as well"""

searchString = []
if isbn := args.get("rft.isbn"):
searchString = [("type", "ISN"), ("lookfor", isbn)]
return urllib.parse.urlencode(searchString)

if title := (
args.get("rft.title")
or args.get("rft.btitle")
or args.get("rft.ctitle")
or args.get("rft.jtitle")
):
aulast = args.get("rft.aulast")
searchString = buildSearchString(title, aulast)
return urllib.parse.urlencode(searchString)

# if nothing matches, just return. no search will be done
return ""


def buildSearchString(title: str, aulast: str = None) -> list:
"""Takes title and aulast (author last name) values from openURL params and builds
the correct search string syntax for VuFind. If aulast is None, only a title search
is built. If an aulast is supplied, a combined title / author search is built.
"""
result = [("type", "title"), ("lookfor", title)]
if aulast:
result = [
("join", "AND"),
("type0[]", "title"),
("lookfor0[]", title),
("type0[]", "author"),
("lookfor0[]", aulast),
]
return result


def lambda_handler(event: dict, context: dict) -> dict:
"""Extracts query string parameters (if any exist) from incoming lambda function URL
request. If there are query string parameters, try to build query string for
Reshare / Vufind. Return an object representing an HTTP redirect response to
ReShare / Vufind, using the query string if one was built.
"""
location = "https://borrowdirect.reshare.indexdata.com/Search/Results?"

if args := event.get("queryStringParameters"):
location = location + query_formatter(args)
response = {
"headers": {"Location": location},
"statusCode": 307,
}
return response

0 comments on commit f198e12

Please sign in to comment.