Skip to content

Commit 213a03a

Browse files
authored
Merge pull request #51 from mikegrima/prepFor1
Fixes and features in preparation for v. 1.0.
2 parents 0b0a468 + 4e3d888 commit 213a03a

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

command_plugins/github/plugin.py

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ def __init__(self):
127127
"help": "Get Deploy Key Public Key",
128128
"enabled": True
129129
},
130+
"!SetTopics": {
131+
"command": "!SetTopics",
132+
"func": self.set_repo_topics_command,
133+
"user_data_required": True,
134+
"help": "Sets the Topics for a GitHub repo",
135+
"enabled": True
136+
}
130137
}
131138
self.token = None
132139

@@ -266,8 +273,8 @@ def set_repo_homepage_command(self, data, user_data, org, repo, homepage):
266273
validation_func=lookup_real_org, validation_func_kwargs={}),
267274
dict(name="repo", properties=dict(type=str, help="The repository to add the outside collaborator to."),
268275
validation_func=extract_repo_name, validation_func_kwargs={}),
269-
dict(name="permission", properties=dict(type=str, help="The permission to grant, must be one "
270-
"of: `{values}`"),
276+
dict(name="permission", properties=dict(type=str.lower, help="The permission to grant, must be one "
277+
"of: `{values}`"),
271278
choices="permitted_permissions"),
272279
],
273280
optional=[]
@@ -323,7 +330,8 @@ def add_outside_collab_command(self, data, user_data, collab, org, repo, permiss
323330
dict(name="org", properties=dict(type=str, help="The organization that contains the team."),
324331
validation_func=lookup_real_org, validation_func_kwargs={}),
325332
dict(name="team", properties=dict(type=str, help="The team to add the user to.")),
326-
dict(name="role", properties=dict(type=str, help="The role to grant the user. Must be one of: `{values}`"),
333+
dict(name="role", properties=dict(type=str.lower, help="The role to grant the user. "
334+
"Must be one of: `{values}`"),
327335
choices="permitted_roles"),
328336
],
329337
optional=[]
@@ -600,7 +608,7 @@ def set_branch_protection_command(self, data, user_data, org, repo, branch, togg
600608
validation_func=lookup_real_org, validation_func_kwargs={}),
601609
dict(name="repo", properties=dict(type=str, help="The name of the repo to list PRs on."),
602610
validation_func=extract_repo_name, validation_func_kwargs={}),
603-
dict(name="state", properties=dict(type=str, help="The state of the PR. Must be one of: `{values}`"),
611+
dict(name="state", properties=dict(type=str.lower, help="The state of the PR. Must be one of: `{values}`"),
604612
choices="permitted_states")
605613
],
606614
optional=[]
@@ -852,6 +860,49 @@ def get_deploy_key_command(self, data, user_data, org, repo, id):
852860
send_info(data["channel"],
853861
"@{}: Deploy Key ID `{}`: ```{}```".format(user_data["name"], id, deploy_key['key']), markdown=True)
854862

863+
@hubcommander_command(
864+
name="!SetTopics",
865+
usage="!SetTopics <OrgThatContainsRepo> <RepoToSetTopicsOn> <CommaSeparatedListOfTopics>",
866+
description="This sets (or clears) the topics for a repository on GitHub.",
867+
required=[
868+
dict(name="org", properties=dict(type=str, help="The organization that contains the repo."),
869+
validation_func=lookup_real_org, validation_func_kwargs={}),
870+
dict(name="repo", properties=dict(type=str, help="The name of the repo to set the topics on."),
871+
lowercase=False, validation_func=extract_repo_name, validation_func_kwargs={}),
872+
873+
],
874+
optional=[
875+
dict(name="topics", properties=dict(nargs="?", default="", type=str,
876+
help="A comma separated list of topics to set on a repo. If"
877+
" omitted, this will clear out the topics."
878+
"Note: This will replace all existing topics."))
879+
]
880+
)
881+
@auth()
882+
def set_repo_topics_command(self, data, user_data, org, repo, topics):
883+
# Make the topics a list:
884+
if topics == "":
885+
topic_list = []
886+
else:
887+
topic_list = topics.split(",")
888+
889+
# Output that we are doing work:
890+
send_info(data["channel"], "@{}: Working, Please wait...".format(user_data["name"]))
891+
892+
# Set the topics:
893+
if self.set_repo_topics(data, user_data, org, repo, topic_list):
894+
# Done:
895+
if len(topic_list) == 0:
896+
send_info(data["channel"],
897+
"@{}: The repo: {repo}'s topics were cleared.".format(user_data["name"], repo=repo),
898+
markdown=True)
899+
900+
else:
901+
send_info(data["channel"],
902+
"@{}: The topics: `{topics}` were applied "
903+
"to the repo: {repo}".format(user_data["name"], topics=",".join(topic_list), repo=repo),
904+
markdown=True)
905+
855906
def check_if_repo_exists(self, data, user_data, reponame, real_org):
856907
try:
857908
result = self.check_gh_for_existing_repo(reponame, real_org)
@@ -921,6 +972,22 @@ def get_repo_prs(self, data, user_data, reponame, real_org, state, **kwargs):
921972
"Here are the details: {}".format(user_data["name"], str(e)))
922973
return False
923974

975+
def set_repo_topics(self, data, user_data, reponame, real_org, topics, **kwargs):
976+
try:
977+
return self.set_repo_topics_http(reponame, real_org, topics, **kwargs)
978+
979+
except requests.exceptions.RequestException as re:
980+
send_error(data["channel"],
981+
"@{}: Problem encountered while setting topics to the repository.\n"
982+
"The response code from GitHub was: {}".format(user_data["name"], str(re)))
983+
return False
984+
985+
except Exception as e:
986+
send_error(data["channel"],
987+
"@{}: Problem encountered while parsing the response.\n"
988+
"Here are the details: {}".format(user_data["name"], str(e)))
989+
return False
990+
924991
def get_repo_deploy_keys(self, data, user_data, reponame, real_org, **kwargs):
925992
try:
926993
return self.get_repo_deploy_keys_http(reponame, real_org, **kwargs)
@@ -1057,6 +1124,35 @@ def get_repo_pull_requests_http(self, repo, org, state, **kwargs):
10571124
.format(response.status_code)
10581125
raise requests.exceptions.RequestException(message)
10591126

1127+
def set_repo_topics_http(self, org, repo, topics, **kwargs):
1128+
"""
1129+
Set topics on a repo.
1130+
See: https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
1131+
:param org:
1132+
:param repo:
1133+
:param topics:
1134+
:param kwargs:
1135+
:return:
1136+
"""
1137+
headers = {
1138+
'Authorization': 'token {}'.format(self.token),
1139+
'Accept': "application/vnd.github.mercy-preview+json"
1140+
}
1141+
1142+
data = {"names": topics}
1143+
1144+
api_part = 'repos/{}/{}/topics'.format(org, repo)
1145+
1146+
response = requests.put('{}{}'.format(GITHUB_URL, api_part), data=json.dumps(data),
1147+
headers=headers, timeout=10)
1148+
1149+
if response.status_code != 200:
1150+
message = 'An error was encountered communicating with GitHub: Status Code: {}' \
1151+
.format(response.status_code)
1152+
raise requests.exceptions.RequestException(message)
1153+
1154+
return True
1155+
10601156
def add_outside_collab_to_repo(self, outside_collab_id, repo_name, real_org, permission):
10611157
headers = {
10621158
'Authorization': 'token {}'.format(self.token),

docs/decorators.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,17 @@ Instead, `@hubcommander_command` has an abstraction layer that can take care of
8686

8787
Here is an example of a multiple choice argument, as seen in the `!ListPRs` command:
8888
```
89-
dict(name="state", properties=dict(type=str, help="The state of the PR. Must be one of: `{values}`"),
89+
dict(name="state", properties=dict(type=str.lower, help="The state of the PR. Must be one of: `{values}`"),
9090
choices="permitted_states")
9191
```
9292

9393
In here, `choices` refers to the name of the `plugin.commands[THECOMMANDHERE][AListOfAvailableChoices]`. It is the name
9494
of the `list` within the plugin's command configuration that contains the available choices for the argument. This is done
9595
because it allows the user of HubCommander to override this list in the plugin's configuration.
9696

97+
Also keep note of the `type`. The `type` in the example above is set to `str.lower`. This is to ensure that you
98+
have case insensitivity in the parsed command. This is documented on StackOverflow [here](https://stackoverflow.com/questions/27616778/case-insensitive-argparse-choices/27616814).
99+
97100
Additionally, the `help` text is altered to include `{values}`. The `@hubcommander_command` decorator will properly format
98101
the help text for that argument and fill in `{values}` with a comma separated list of the values in the specified list.
99102

tests/test_decorators.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def test_help_command_with_list(user_data, slack_client):
273273
usage="!TestCommand <testThing>",
274274
description="This is a test command to test help text for things in lists",
275275
required=[
276-
dict(name="test_thing", properties=dict(type=str, help="Must be one of: `{values}`"),
276+
dict(name="test_thing", properties=dict(type=str.lower, help="Must be one of: `{values}`"),
277277
choices="valid_values")
278278
]
279279
)
@@ -292,9 +292,15 @@ def the_command(self, data, user_data, test_thing):
292292

293293
tc = TestCommands()
294294

295+
# Will assert True
295296
data = dict(text="!TestCommand one")
296297
tc.the_command(data, user_data)
297298

299+
# Will ALSO assert True... we are making sure to lowercase the choices with str.lower as the type:
300+
data = dict(text="!TestCommand ThReE")
301+
tc.the_command(data, user_data)
302+
303+
# Will NOT assert true -- this will output help text:
298304
data = dict(text="!TestCommand", channel="12345")
299305
tc.the_command(data, user_data)
300306

@@ -307,6 +313,7 @@ def the_command(self, data, user_data, test_thing):
307313
slack_client.api_call.assert_called_with("chat.postMessage", channel="12345", as_user=True,
308314
attachments=json.dumps([attachment]), text=" ")
309315

316+
# Will NOT assert true
310317
data = dict(text="!TestCommand alskjfasdlkf", channel="12345")
311318
tc.the_command(data, user_data)
312319
attachment = {

0 commit comments

Comments
 (0)