diff --git a/Pipfile b/Pipfile index 93dae33..54a2ea2 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,7 @@ flake8-comprehensions = "*" flake8-mutable = "*" flake8-print = "*" flake8-tuple = "*" +black = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 8479865..0e66998 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "61ea95b79f952977fd27ba5047da2443b0fc81683a47f79bc99c5f03334bb0ba" + "sha256": "d6d1208a530bba57b583478e35d644c0d4e881449bb948d334f745b70989a860" }, "pipfile-spec": 6, "requires": { @@ -223,6 +223,35 @@ } }, "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "black": { + "hashes": [ + "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", + "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5" + ], + "index": "pypi", + "version": "==18.9b0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, "coverage": { "hashes": [ "sha256:029c69deaeeeae1b15bc6c59f0ffa28aa8473721c614a23f2c2976dec245cd12", @@ -361,6 +390,13 @@ ], "version": "==1.12.0" }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, "typed-ast": { "hashes": [ "sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23", diff --git a/actioner/clients.py b/actioner/clients.py index 6e84287..ac47fd3 100644 --- a/actioner/clients.py +++ b/actioner/clients.py @@ -7,4 +7,6 @@ from todoist import TodoistAPI from actioner.settings import GITHUB_TOKEN, TODOIST_TOKEN github = Github(GITHUB_TOKEN) -todoist = TodoistAPI(TODOIST_TOKEN, cache=os.path.join(tempfile.gettempdir(), 'todoist-api')) +todoist = TodoistAPI( + TODOIST_TOKEN, cache=os.path.join(tempfile.gettempdir(), "todoist-api") +) diff --git a/actioner/main.py b/actioner/main.py index 4802217..ff96437 100644 --- a/actioner/main.py +++ b/actioner/main.py @@ -13,10 +13,7 @@ from actioner.web import get_server def main(): logging.basicConfig(level=LOGGING_LEVEL) - sentry_sdk.init( - dsn=SENTRY_DSN, - integrations=[AioHttpIntegration()] - ) + sentry_sdk.init(dsn=SENTRY_DSN, integrations=[AioHttpIntegration()]) server = get_server() scheduler = create_scheduler() @@ -26,5 +23,5 @@ def main(): Process(target=start_scheduler, args=(scheduler,)).start() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/actioner/scheduler/__init__.py b/actioner/scheduler/__init__.py index f0c63fa..2f82a56 100644 --- a/actioner/scheduler/__init__.py +++ b/actioner/scheduler/__init__.py @@ -9,8 +9,8 @@ from .todoist_repo_prs import todoist_repo_prs def create_scheduler(): scheduler = AsyncIOScheduler() - scheduler.add_job(todoist_assigned_issues, 'interval', minutes=15) - scheduler.add_job(todoist_repo_prs, 'interval', minutes=15) + scheduler.add_job(todoist_assigned_issues, "interval", minutes=15) + scheduler.add_job(todoist_repo_prs, "interval", minutes=15) for job in scheduler.get_jobs(): if isinstance(job.trigger, IntervalTrigger): diff --git a/actioner/scheduler/todoist_assigned_issues.py b/actioner/scheduler/todoist_assigned_issues.py index d6f0beb..284490d 100644 --- a/actioner/scheduler/todoist_assigned_issues.py +++ b/actioner/scheduler/todoist_assigned_issues.py @@ -6,25 +6,15 @@ from actioner.clients import github, todoist from actioner.utils import get_todoist_project_from_repo from actioner.utils.github import get_existing_task, get_issue_link -REPOS = frozenset([ - 'srobo/tasks', - 'srobo/core-team-minutes' -]) +REPOS = frozenset(["srobo/tasks", "srobo/core-team-minutes"]) -LABEL_TO_STATUS = { - 'must have': 4, - 'critical': 4, - 'should have': 2 -} +LABEL_TO_STATUS = {"must have": 4, "critical": 4, "should have": 2} logger = logging.getLogger(__name__) def get_status_for_issue(issue: Issue) -> int: - priorities = { - LABEL_TO_STATUS.get(label.name.lower(), 1) - for label in issue.labels - } + priorities = {LABEL_TO_STATUS.get(label.name.lower(), 1) for label in issue.labels} return max(priorities, default=1) @@ -38,9 +28,13 @@ def todoist_assigned_issues(): todoist.items.sync() for repo_name in REPOS: project_id = get_todoist_project_from_repo(repo_name) - existing_tasks = {item['id']: item['content'] for item in todoist.state['items'] if item['project_id'] == project_id} + existing_tasks = { + item["id"]: item["content"] + for item in todoist.state["items"] + if item["project_id"] == project_id + } repo = github.get_repo(repo_name) - for issue in repo.get_issues(assignee=me.login, state='all'): + for issue in repo.get_issues(assignee=me.login, state="all"): me_assigned = me.login in {assignee.login for assignee in issue.assignees} existing_task_id = get_existing_task(existing_tasks, issue) @@ -49,29 +43,34 @@ def todoist_assigned_issues(): todoist.items.delete([existing_task_id]) continue - elif issue.state == 'closed' and existing_task_id is not None and not todoist.items.get_by_id(existing_task_id)['checked']: + elif ( + issue.state == "closed" + and existing_task_id is not None + and not todoist.items.get_by_id(existing_task_id)["checked"] + ): logger.info("Completing task for '{}'".format(issue.title)) todoist.items.complete([existing_task_id]) continue - if issue.state == 'open': + if issue.state == "open": if existing_task_id is None: logger.info("Creating task for '{}'".format(issue.title)) existing_task_id = todoist.items.add( - issue_to_task_name(issue), - project_id - )['id'] + issue_to_task_name(issue), project_id + )["id"] existing_task = todoist.items.get_by_id(existing_task_id) - if existing_task['checked']: + if existing_task["checked"]: logger.info("Re-opening task '{}'".format(issue.title)) todoist.items.uncomplete([existing_task_id]) existing_task.update( content=issue_to_task_name(issue), - priority=get_status_for_issue(issue) + priority=get_status_for_issue(issue), ) if issue.milestone and issue.milestone.due_on: - existing_task.update(date_string=issue.milestone.due_on.strftime("%d/%m/%Y")) + existing_task.update( + date_string=issue.milestone.due_on.strftime("%d/%m/%Y") + ) todoist.commit() diff --git a/actioner/scheduler/todoist_repo_prs.py b/actioner/scheduler/todoist_repo_prs.py index f0253bd..1c8f36f 100644 --- a/actioner/scheduler/todoist_repo_prs.py +++ b/actioner/scheduler/todoist_repo_prs.py @@ -9,9 +9,7 @@ from actioner.utils.github import get_existing_task, get_issue_link logger = logging.getLogger(__name__) -REPOS = frozenset([ - 'srobo/core-team-minutes' -]) +REPOS = frozenset(["srobo/core-team-minutes"]) def pr_to_task_name(pr: PullRequest) -> str: @@ -30,41 +28,47 @@ def todoist_repo_prs(): todoist.items.sync() for repo_name in REPOS: project_id = get_todoist_project_from_repo(repo_name) - existing_tasks = {item['id']: item['content'] for item in todoist.state['items'] if item['project_id'] == project_id} + existing_tasks = { + item["id"]: item["content"] + for item in todoist.state["items"] + if item["project_id"] == project_id + } repo = github.get_repo(repo_name) - for pr in repo.get_pulls(state='all'): + for pr in repo.get_pulls(state="all"): existing_task_id = get_existing_task(existing_tasks, pr) - if pr.state == 'closed' and existing_task_id: + if pr.state == "closed" and existing_task_id: my_review = get_my_review(me, pr) - if pr.merged and my_review and my_review.state == 'APPROVED': + if pr.merged and my_review and my_review.state == "APPROVED": logger.info("Completing task to review '{}'".format(pr.title)) todoist.items.complete([existing_task_id]) else: logger.info("Deleting task to review '{}'".format(pr.title)) todoist.items.delete([existing_task_id]) - elif pr.state == 'open': + elif pr.state == "open": if existing_task_id is None: logger.info("Creating task to review '{}'".format(pr.title)) existing_task_id = todoist.items.add( - pr_to_task_name(pr), - project_id - )['id'] + pr_to_task_name(pr), project_id + )["id"] existing_task = todoist.items.get_by_id(existing_task_id) my_review = get_my_review(me, pr) if existing_task_id and my_review: - if my_review.commit_id == pr.head.sha and not existing_task['checked']: + if ( + my_review.commit_id == pr.head.sha + and not existing_task["checked"] + ): logger.info("Completing task to review '{}'".format(pr.title)) todoist.items.complete([existing_task_id]) - elif existing_task['checked'] and existing_task['checked']: + elif existing_task["checked"] and existing_task["checked"]: logger.info("Re-opening task to review '{}'".format(pr.title)) todoist.items.uncomplete([existing_task_id]) - existing_task.update( - content=pr_to_task_name(pr) - ) + existing_task.update(content=pr_to_task_name(pr)) if pr.milestone and pr.milestone.due_on: - existing_task.update(date_string=pr.milestone.due_on.strftime("%d/%m/%Y")) + existing_task.update( + date_string=pr.milestone.due_on.strftime("%d/%m/%Y") + ) todoist.commit() diff --git a/actioner/settings.py b/actioner/settings.py index f890876..34b1eee 100644 --- a/actioner/settings.py +++ b/actioner/settings.py @@ -1,12 +1,12 @@ import os from logging import _nameToLevel -GITHUB_TOKEN = os.environ['GITHUB_TOKEN'] +GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] -TODOIST_TOKEN = os.environ['TODOIST_TOKEN'] +TODOIST_TOKEN = os.environ["TODOIST_TOKEN"] -SENTRY_DSN = os.environ.get('SENTRY_DSN', '') +SENTRY_DSN = os.environ.get("SENTRY_DSN", "") -LOGGING_LEVEL = _nameToLevel[os.environ.get('LOGGING_LEVEL', 'INFO')] +LOGGING_LEVEL = _nameToLevel[os.environ.get("LOGGING_LEVEL", "INFO")] -BASIC_AUTH = os.environ['BASIC_AUTH'].split(":") +BASIC_AUTH = os.environ["BASIC_AUTH"].split(":") diff --git a/actioner/utils/__init__.py b/actioner/utils/__init__.py index 90ee80a..bbb53f1 100644 --- a/actioner/utils/__init__.py +++ b/actioner/utils/__init__.py @@ -1,17 +1,14 @@ from typing import Dict, Optional -GH_REPO_TO_TODOIST = { -} # type: Dict[str, int] +GH_REPO_TO_TODOIST = {} # type: Dict[str, int] -GH_ORG_TO_TODOIST = { - 'srobo': 2190856871 -} # type: Dict[str, int] +GH_ORG_TO_TODOIST = {"srobo": 2190856871} # type: Dict[str, int] def get_todoist_project_from_repo(repo_name: str) -> Optional[int]: repo_id = GH_REPO_TO_TODOIST.get(repo_name) if repo_id is not None: return repo_id - org = repo_name.split('/')[0] + org = repo_name.split("/")[0] return GH_ORG_TO_TODOIST.get(org) diff --git a/actioner/utils/github.py b/actioner/utils/github.py index 758a28b..19d23fa 100644 --- a/actioner/utils/github.py +++ b/actioner/utils/github.py @@ -2,10 +2,7 @@ from typing import Dict def get_issue_link(issue_or_pr) -> str: - return "[#{id}]({url})".format( - id=issue_or_pr.number, - url=issue_or_pr.html_url - ) + return "[#{id}]({url})".format(id=issue_or_pr.number, url=issue_or_pr.html_url) def get_existing_task(tasks: Dict[int, str], issue_or_pr): diff --git a/actioner/web/__init__.py b/actioner/web/__init__.py index 9be00db..d155252 100644 --- a/actioner/web/__init__.py +++ b/actioner/web/__init__.py @@ -7,7 +7,9 @@ from .healthcheck import healthcheck def get_server(): - auth = BasicAuthMiddleware(username=BASIC_AUTH[0], password=BASIC_AUTH[1], force=False) + auth = BasicAuthMiddleware( + username=BASIC_AUTH[0], password=BASIC_AUTH[1], force=False + ) app = web.Application() app.router.add_get("/healthcheck", auth.required(healthcheck)) return app diff --git a/actioner/web/healthcheck.py b/actioner/web/healthcheck.py index 119af8d..07df402 100644 --- a/actioner/web/healthcheck.py +++ b/actioner/web/healthcheck.py @@ -5,7 +5,6 @@ from actioner.clients import github, todoist async def healthcheck(request): todoist.user.sync() - return web.json_response({ - 'github': github.get_user().login, - 'todoist': todoist.user.get_id() - }) + return web.json_response( + {"github": github.get_user().login, "todoist": todoist.user.get_id()} + ) diff --git a/scripts/test-fix.sh b/scripts/test-fix.sh index 4c555f0..357b76b 100755 --- a/scripts/test-fix.sh +++ b/scripts/test-fix.sh @@ -3,3 +3,5 @@ set -e isort -rc actioner/ tests/ + +black actioner/ tests/ diff --git a/scripts/test.sh b/scripts/test.sh index 233c1fe..a98691b 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -5,6 +5,9 @@ set -e echo "> Running tests..." nose2 $@ -C --coverage actioner --verbose --coverage-report term --coverage-report html +echo "> Running formatter..." +black actioner/ tests/ --check + echo "> Running isort" isort -rc -c actioner/ tests/ diff --git a/tests/test_utils/test_github.py b/tests/test_utils/test_github.py index 846ade6..cabc213 100644 --- a/tests/test_utils/test_github.py +++ b/tests/test_utils/test_github.py @@ -3,34 +3,33 @@ from collections import namedtuple from actioner.utils.github import get_existing_task, get_issue_link from tests import BaseTestCase -FakeIssue = namedtuple('FakeIssue', ['number', 'html_url', 'title']) +FakeIssue = namedtuple("FakeIssue", ["number", "html_url", "title"]) class IssueLinkTestCase(BaseTestCase): def setUp(self): super().setUp() - self.issue = FakeIssue(123, 'https://github.com/repo/thing', 'issue title') + self.issue = FakeIssue(123, "https://github.com/repo/thing", "issue title") def test_creates_link(self): - self.assertEqual(get_issue_link(self.issue), "[#123](https://github.com/repo/thing)") + self.assertEqual( + get_issue_link(self.issue), "[#123](https://github.com/repo/thing)" + ) class ExistingTaskTestCase(BaseTestCase): def setUp(self): super().setUp() self.tasks = { - 123: '[#1](url): title', - 456: '[#2](url/2): title 2', - 789: '[#3](url/3): title 3', + 123: "[#1](url): title", + 456: "[#2](url/2): title 2", + 789: "[#3](url/3): title 3", } def test_finds_existing_repos(self): self.assertEqual( - get_existing_task(self.tasks, FakeIssue(1, 'url', 'title')), - 123 + get_existing_task(self.tasks, FakeIssue(1, "url", "title")), 123 ) def test_not_existing_repo(self): - self.assertIsNone( - get_existing_task(self.tasks, FakeIssue(123, 'url', 'title')) - ) + self.assertIsNone(get_existing_task(self.tasks, FakeIssue(123, "url", "title"))) diff --git a/tests/test_utils/test_init.py b/tests/test_utils/test_init.py index 7cfc76d..bc4dbbe 100644 --- a/tests/test_utils/test_init.py +++ b/tests/test_utils/test_init.py @@ -18,7 +18,10 @@ class TodoistProjectToRepoTestCase(BaseTestCase): def test_gets_correct_project_for_org(self): for org_name, project_id in GH_ORG_TO_TODOIST.items(): - self.assertEqual(get_todoist_project_from_repo("{}/test_repo".format(org_name)), project_id) + self.assertEqual( + get_todoist_project_from_repo("{}/test_repo".format(org_name)), + project_id, + ) def test_organization_exists(self): for org in GH_ORG_TO_TODOIST.keys(): @@ -27,6 +30,6 @@ class TodoistProjectToRepoTestCase(BaseTestCase): def test_project_exists(self): project_ids = set(GH_ORG_TO_TODOIST.values()).union(GH_REPO_TO_TODOIST.values()) todoist.projects.sync() - todoist_project_ids = {project['id'] for project in todoist.state['projects']} + todoist_project_ids = {project["id"] for project in todoist.state["projects"]} for project in project_ids: self.assertIn(project, todoist_project_ids) diff --git a/tests/test_web/test_healthcheck.py b/tests/test_web/test_healthcheck.py index 0c98019..f3f4563 100644 --- a/tests/test_web/test_healthcheck.py +++ b/tests/test_web/test_healthcheck.py @@ -10,18 +10,17 @@ class HealthcheckTestCase(BaseWebTestCase): async def test_heartbeat_requires_auth(self): response = await self.client.get("/healthcheck") self.assertEqual(response.status, 401) - response = await self.client.get("/healthcheck", headers={ - 'Authorization': BasicAuth(*BASIC_AUTH).encode() - }) + response = await self.client.get( + "/healthcheck", headers={"Authorization": BasicAuth(*BASIC_AUTH).encode()} + ) self.assertEqual(response.status, 200) @unittest_run_loop async def test_heartbeat_response(self): - response = await self.client.get("/healthcheck", headers={ - 'Authorization': BasicAuth(*BASIC_AUTH).encode() - }) + response = await self.client.get( + "/healthcheck", headers={"Authorization": BasicAuth(*BASIC_AUTH).encode()} + ) self.assertEqual(response.status, 200) - self.assertEqual(await response.json(), { - 'github': 'RealOrangeOne', - 'todoist': 7471233 - }) + self.assertEqual( + await response.json(), {"github": "RealOrangeOne", "todoist": 7471233} + )