Skip to content

Commit e2162f8

Browse files
committed
Add basic projects support
1 parent 9d9d1e9 commit e2162f8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+520
-217
lines changed

.github/workflows/main.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: MythX CLI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: [3.6, 3.7, 3.8, pypy3]
11+
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Set up Python ${{ matrix.python-version }}
15+
uses: actions/setup-python@v2
16+
with:
17+
python-version: ${{ matrix.python-version }}
18+
- name: Install Python dependencies
19+
uses: py-actions/py-dependency-install@v2
20+
with:
21+
path: "requirements_dev.txt"
22+
- name: Setup tox for GH actions
23+
run: pip install tox-gh-actions
24+
- name: Test with tox
25+
run: make test
26+
- name: Upload to Coveralls
27+
run: coveralls
28+
env:
29+
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
30+
31+
deploy:
32+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
33+
needs: test
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v2
37+
- name: Set up Python
38+
uses: actions/setup-python@v2
39+
with:
40+
python-version: "3.7"
41+
- name: Install Python dependencies
42+
uses: py-actions/py-dependency-install@v2
43+
with:
44+
path: "requirements_dev.txt"
45+
- name: Build and publish
46+
env:
47+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
48+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
49+
run: make release

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ lint: ## check style with flake8
5858
flake8 mythx_cli tests
5959

6060
test: ## run tests quickly with the default Python
61-
py.test -vv
61+
pytest --cov-report html --cov-report term --cov mythx_cli tests/
6262

6363
test-all: ## run tests on every Python version with tox
6464
tox

mythx_cli/analysis/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ def analysis_list(ctx, number: int) -> None:
4444

4545
# trim result to desired result number
4646
LOGGER.debug(f"Got {len(result.analyses)} analyses, trimming to {number}")
47-
result = AnalysisListResponse(analyses=result[:number], total=resp.total)
47+
result = AnalysisListResponse(analyses=result.analyses[:number], total=resp.total)
4848
write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_list(result))

mythx_cli/analysis/report.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import click
66
from mythx_models.response import AnalysisInputResponse, DetectedIssuesResponse
7+
from pythx import Client
78

89
from mythx_cli.formatter import FORMAT_RESOLVER, util
910
from mythx_cli.formatter.base import BaseFormatter
@@ -54,9 +55,10 @@ def analysis_report(
5455
"""
5556

5657
issues_list: List[
57-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
58+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
5859
] = []
5960
formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]]
61+
ctx["client"]: Client
6062
for uuid in uuids:
6163
LOGGER.debug(f"{uuid}: Fetching report")
6264
resp = ctx["client"].report(uuid)
@@ -74,8 +76,7 @@ def analysis_report(
7476
swc_blacklist=swc_blacklist,
7577
swc_whitelist=swc_whitelist,
7678
)
77-
resp.uuid = uuid
78-
issues_list.append((resp, inp))
79+
issues_list.append((uuid, resp, inp))
7980

8081
LOGGER.debug(
8182
f"{uuid}: Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\""

mythx_cli/analysis/status.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ def analysis_status(ctx, uuids: List[str]) -> None:
2222
"""
2323
for uuid in uuids:
2424
LOGGER.debug(f"{uuid}: Fetching status")
25-
resp = ctx["client"].status(uuid)
25+
resp = ctx["client"].analysis_status(uuid)
2626
LOGGER.debug(f"{uuid}: Printing status information")
2727
write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_status(resp))

mythx_cli/analyze/command.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ def analyze(
206206

207207
if create_group:
208208
resp: GroupCreationResponse = ctx["client"].create_group(group_name=group_name)
209-
group_id = resp.group.identifier
210-
group_name = resp.group.name or ""
209+
group_id = resp.identifier
210+
group_name = resp.name or ""
211211

212212
if group_id:
213213
# associate all following analyses to the passed or newly created group
@@ -308,7 +308,7 @@ def analyze(
308308
return
309309

310310
issues_list: List[
311-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
311+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
312312
] = []
313313
formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]]
314314
for uuid in uuids:
@@ -331,8 +331,8 @@ def analyze(
331331
swc_whitelist=swc_whitelist,
332332
)
333333
# extend response with job UUID to keep formatter logic isolated
334-
resp.uuid = uuid
335-
issues_list.append((resp, inp))
334+
# resp.uuid = uuid
335+
issues_list.append((uuid, resp, inp))
336336

337337
LOGGER.debug(
338338
f"Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\""

mythx_cli/cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from mythx_cli.group.list import group_list
1919
from mythx_cli.group.open import group_open
2020
from mythx_cli.group.status import group_status
21+
from mythx_cli.project.list import project_list
2122
from mythx_cli.render.command import render
2223
from mythx_cli.util import update_context
2324
from mythx_cli.version.command import version
@@ -205,6 +206,25 @@ def cli(
205206
cli.add_command(version)
206207

207208

209+
@cli.group()
210+
def project() -> None:
211+
"""Create, modify, and view analysis projects.
212+
213+
\f
214+
215+
This subcommand holds all project-related actions, such as creating,
216+
listing, and managing projects, as well as fetching the status of one
217+
or more groups inside a project.
218+
"""
219+
pass
220+
221+
222+
from mythx_cli.project import project_list
223+
224+
LOGGER.debug("Registering project commands")
225+
project.add_command(project_list)
226+
227+
208228
@cli.group()
209229
def group() -> None:
210230
"""Create, modify, and view analysis groups.

mythx_cli/formatter/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str:
3434
@abc.abstractmethod
3535
def format_detected_issues(
3636
issues_list: List[
37-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
37+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
3838
],
3939
**kwargs,
4040
) -> str:

mythx_cli/formatter/json.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
DetectedIssuesResponse,
1111
GroupListResponse,
1212
GroupStatusResponse,
13+
ProjectListResponse,
1314
VersionResponse,
1415
)
1516

@@ -29,43 +30,49 @@ class JSONFormatter(BaseFormatter):
2930
def format_group_status(resp: GroupStatusResponse) -> str:
3031
"""Format a group status response as compressed JSON."""
3132

32-
return resp.to_json()
33+
return resp.json(by_alias=True)
34+
35+
@staticmethod
36+
def format_project_list(resp: ProjectListResponse):
37+
"""Format a project list response as pretty-printed JSON."""
38+
39+
return resp.json(by_alias=True)
3340

3441
@staticmethod
3542
def format_group_list(resp: GroupListResponse) -> str:
3643
"""Format a group list response as compressed JSON."""
3744

38-
return resp.to_json()
45+
return resp.json(by_alias=True)
3946

4047
@staticmethod
4148
def format_analysis_list(resp: AnalysisListResponse) -> str:
4249
"""Format an analysis list response as compressed JSON."""
4350

44-
return resp.to_json()
51+
return resp.json(by_alias=True)
4552

4653
@staticmethod
4754
def format_analysis_status(resp: AnalysisStatusResponse) -> str:
4855
"""Format an analysis status response as compressed JSON."""
4956

50-
return resp.to_json()
57+
return resp.json(by_alias=True)
5158

5259
@staticmethod
5360
def format_detected_issues(
5461
issues_list: List[
55-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
62+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
5663
],
5764
**kwargs,
5865
) -> str:
5966
"""Format an issue report response as compressed JSON."""
6067

61-
output = [resp.to_dict(as_list=True) for resp, _ in issues_list]
68+
output = [resp.dict(by_alias=True) for _, resp, _ in issues_list]
6269
return json.dumps(output)
6370

6471
@staticmethod
6572
def format_version(resp: VersionResponse) -> str:
6673
"""Format a version response as compressed JSON."""
6774

68-
return resp.to_json()
75+
return resp.json(by_alias=True)
6976

7077

7178
class PrettyJSONFormatter(BaseFormatter):
@@ -85,9 +92,15 @@ def _print_as_json(obj, report_mode=False) -> str:
8592
json_args = {"indent": 2, "sort_keys": True}
8693
if report_mode:
8794
return json.dumps(
88-
[resp.to_dict(as_list=True) for resp, _ in obj], **json_args
95+
[resp.dict(by_alias=True) for _, resp, _ in obj], **json_args
8996
)
90-
return json.dumps(obj.to_dict(), **json_args)
97+
return json.dumps(obj.dict(by_alias=True), **json_args)
98+
99+
@staticmethod
100+
def format_project_list(resp: ProjectListResponse):
101+
"""Format a project list response as pretty-printed JSON."""
102+
103+
return PrettyJSONFormatter._print_as_json(resp)
91104

92105
@staticmethod
93106
def format_group_status(resp: GroupStatusResponse) -> str:
@@ -116,7 +129,7 @@ def format_analysis_status(obj: AnalysisStatusResponse) -> str:
116129
@staticmethod
117130
def format_detected_issues(
118131
issues_list: List[
119-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
132+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
120133
],
121134
**kwargs,
122135
) -> str:

mythx_cli/formatter/simple_stdout.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
DetectedIssuesResponse,
1111
GroupListResponse,
1212
GroupStatusResponse,
13+
ProjectListResponse,
1314
VersionResponse,
1415
)
1516

@@ -33,7 +34,7 @@ def format_analysis_list(resp: AnalysisListResponse) -> str:
3334
"""Format an analysis list response to a simple text representation."""
3435

3536
res = []
36-
for analysis in resp:
37+
for analysis in resp.analyses:
3738
res.append("UUID: {}".format(analysis.uuid))
3839
res.append("Submitted at: {}".format(analysis.submitted_at))
3940
res.append("Status: {}".format(analysis.status))
@@ -46,10 +47,10 @@ def format_group_status(resp: GroupStatusResponse) -> str:
4647
"""Format a group status response to a simple text representation."""
4748

4849
res = [
49-
"ID: {}".format(resp.group.identifier),
50-
"Name: {}".format(resp.group.name or "<unnamed>"),
51-
"Created on: {}".format(resp.group.created_at),
52-
"Status: {}".format(resp.group.status),
50+
"ID: {}".format(resp.identifier),
51+
"Name: {}".format(resp.name or "<unnamed>"),
52+
"Created on: {}".format(resp.created_at),
53+
"Status: {}".format(resp.status),
5354
"",
5455
]
5556
return "\n".join(res)
@@ -60,7 +61,7 @@ def format_group_list(resp: GroupListResponse) -> str:
6061
representation."""
6162

6263
res = []
63-
for group in resp:
64+
for group in resp.groups:
6465
res.append("ID: {}".format(group.identifier))
6566
res.append("Name: {}".format(group.name or "<unnamed>"))
6667
res.append("Created on: {}".format(group.created_at))
@@ -69,6 +70,21 @@ def format_group_list(resp: GroupListResponse) -> str:
6970

7071
return "\n".join(res)
7172

73+
@staticmethod
74+
def format_project_list(resp: ProjectListResponse) -> str:
75+
"""Format an analysis group response to a simple text
76+
representation."""
77+
78+
res = []
79+
for project in resp.projects:
80+
res.append("ID: {}".format(project.id))
81+
res.append("Name: {}".format(project.name or "<unnamed>"))
82+
res.append("Created on: {}".format(project.created))
83+
res.append("Modified: {}".format(project.modified))
84+
res.append("")
85+
86+
return "\n".join(res)
87+
7288
@staticmethod
7389
def format_analysis_status(resp: AnalysisStatusResponse) -> str:
7490
"""Format an analysis status response to a simple text
@@ -85,7 +101,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str:
85101
@staticmethod
86102
def format_detected_issues(
87103
issues_list: List[
88-
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
104+
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
89105
],
90106
**kwargs,
91107
) -> str:
@@ -113,10 +129,10 @@ def format_version(resp: VersionResponse) -> str:
113129

114130
return "\n".join(
115131
[
116-
"API: {}".format(resp.api_version),
117-
"Harvey: {}".format(resp.harvey_version),
118-
"Maru: {}".format(resp.maru_version),
119-
"Mythril: {}".format(resp.mythril_version),
120-
"Hashed: {}".format(resp.hashed_version),
132+
"API: {}".format(resp.api),
133+
"Harvey: {}".format(resp.harvey),
134+
"Maru: {}".format(resp.maru),
135+
"Mythril: {}".format(resp.mythril),
136+
"Hashed: {}".format(resp.hash),
121137
]
122138
)

0 commit comments

Comments
 (0)