-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
d572379
commit f198e12
Showing
12 changed files
with
1,012 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.9.12 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 . | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |