Skip to content

Commit 8a40d0c

Browse files
authored
Merge pull request #1 from bugout-dev/skeleton
Skeleton of API
2 parents 06c1d7a + c7d72b6 commit 8a40d0c

File tree

13 files changed

+548
-1
lines changed

13 files changed

+548
-1
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,13 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# Pycharm
132+
.idea
133+
134+
# VS Code
135+
.vscode/
136+
137+
# Custom
138+
.secrets/
139+
.bugout/

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
# bugout-python
2-
Python client library for Bugout
2+
Python client library for Bugout API
3+
4+
```python
5+
from bugout.app import Bugout
6+
7+
def main():
8+
bugout = Bugout(brood_api_url="http://localhost:9001", spire_api_url="http://localhost:9002")
9+
user = bugout.get_user("<user token id>")
10+
group = bugout.get_group("<group id>", "<user token id>")
11+
12+
if __name__ == "__main__":
13+
main()
14+
```

bugout/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
Bugout Python API
3+
"""
4+
__author__ = "Bugout"
5+
__maintainer__ = __author__
6+
7+
__email__ = "engineering@bugout.dev"
8+
__license__ = "MIT"
9+
__version__ = "0.0.1"
10+
11+
__all__ = (
12+
"__author__",
13+
"__email__",
14+
"__license__",
15+
"__maintainer__",
16+
"__version__",
17+
)

bugout/__main__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import argparse
2+
import logging
3+
import textwrap
4+
5+
from .app import Bugout
6+
7+
logging.basicConfig(level=logging.INFO)
8+
logger = logging.getLogger(__name__)
9+
10+
11+
def user_get_handler(args: argparse.Namespace) -> None:
12+
"""
13+
Handler for "users get" subcommand.
14+
"""
15+
bugout = Bugout(brood_api_url=args.brood_url, spire_api_url=args.spire_url)
16+
result = bugout.get_user(token=args.token)
17+
print(result.json())
18+
19+
20+
def main() -> None:
21+
bugout_description = textwrap.dedent(
22+
"""\
23+
Bugout API: Tools for helping with Bugout API.
24+
"""
25+
)
26+
parser = argparse.ArgumentParser(prog="bugout", description=bugout_description)
27+
parser.set_defaults(func=lambda _: parser.print_help())
28+
subcommands = parser.add_subparsers(description="Journal commands")
29+
30+
parser.add_argument(
31+
"--brood_url",
32+
default="http://localhost",
33+
help="Brood url",
34+
)
35+
parser.add_argument(
36+
"--spire_url",
37+
default="http://localhost",
38+
help="Spire url",
39+
)
40+
41+
# Users handlers
42+
parser_users = subcommands.add_parser(
43+
"user", description="Work with Bugout users API handlers"
44+
)
45+
parser_users.set_defaults(
46+
func=lambda _: parser_users.print_help(), subparser="users"
47+
)
48+
subcommands_users = parser_users.add_subparsers(description="Users commands")
49+
50+
parser_user_get = subcommands_users.add_parser("get", description="Get user")
51+
parser_user_get.set_defaults(subcommand="get")
52+
parser_user_get.add_argument(
53+
"-t",
54+
"--token",
55+
required=True,
56+
help="User token",
57+
)
58+
parser_user_get.set_defaults(func=user_get_handler)
59+
60+
args = parser.parse_args()
61+
args.func(args)
62+
63+
64+
if __name__ == "__main__":
65+
main()

bugout/app.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from typing import Any, Dict, List, Optional, Tuple
2+
3+
from . import data
4+
from . import calls
5+
from .group import Group
6+
from .journal import Journal
7+
from .user import User
8+
9+
10+
class Bugout:
11+
def __init__(
12+
self, brood_api_url: Optional[str] = None, spire_api_url: Optional[str] = None
13+
) -> None:
14+
self.brood_api_url = brood_api_url
15+
self.spire_api_url = spire_api_url
16+
17+
self.user = User(self.brood_api_url)
18+
self.group = Group(self.brood_api_url)
19+
self.journal = Journal(self.spire_api_url)
20+
21+
@property
22+
def brood_url(self):
23+
return self.brood_api_url
24+
25+
@property
26+
def spire_url(self):
27+
return self.spire_api_url
28+
29+
def brood_ping(self) -> Dict[str, str]:
30+
return calls.ping(self.brood_api_url)
31+
32+
def spire_ping(self) -> Dict[str, str]:
33+
return calls.ping(self.spire_api_url)
34+
35+
# User handlers
36+
def get_user(self, token: str) -> data.BugoutUser:
37+
return self.user.get_user(token)
38+
39+
def create_user(
40+
self,
41+
username: str,
42+
email: str,
43+
password: str,
44+
autogenerated_token: str = None,
45+
) -> data.BugoutUser:
46+
return self.user.create_user(
47+
username, email, password, autogenerated_token=autogenerated_token
48+
)
49+
50+
def create_token(self, username: str, password: str) -> data.BugoutToken:
51+
return self.user.create_token(username, password)
52+
53+
def revoke_token(self, token: str) -> data.BugoutToken:
54+
return self.user.revoke_token(token)
55+
56+
def update_token(
57+
self,
58+
token: str,
59+
token_type: Optional[str] = None,
60+
token_note: Optional[str] = None,
61+
) -> data.BugoutToken:
62+
return self.user.update_token(token, token_type, token_note)
63+
64+
# Group handlers
65+
def get_group(self, group_id: str, token: str) -> data.BugoutGroup:
66+
return self.group.get_group(group_id, token)
67+
68+
# Journal handlers
69+
def get_journal(self, journal_id: str, token: str) -> data.BugoutJournal:
70+
return self.journal.get_journal(journal_id, token)

bugout/calls.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import logging
2+
from typing import Any, Dict, List, Optional, Tuple
3+
4+
import requests
5+
6+
from .data import Method
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class InvalidUrlSpec(ValueError):
12+
"""
13+
Raised when an invalid url is specified.
14+
"""
15+
16+
17+
class BugoutUnexpectedResponse(Exception):
18+
"""
19+
Raised when Bugout server response is unexpected (e.g. unparseable).
20+
"""
21+
22+
23+
def make_request(method: Method, url: str, **kwargs) -> Any:
24+
response_body = None
25+
try:
26+
r = requests.request(method.value, url=url, **kwargs)
27+
r.raise_for_status()
28+
response_body = r.json()
29+
except Exception as e:
30+
logger.error(f"Exception {str(e)}")
31+
raise
32+
return response_body
33+
34+
35+
def ping(url: str) -> Dict[str, Any]:
36+
url = f"{url.rstrip('/')}/ping"
37+
return make_request(Method.get, url)

bugout/data.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from datetime import datetime
2+
from enum import Enum, unique
3+
from typing import Any, Dict, List, Optional, Tuple
4+
import uuid
5+
6+
from pydantic import BaseModel
7+
8+
9+
@unique
10+
class Method(Enum):
11+
delete = "delete"
12+
get = "get"
13+
post = "post"
14+
put = "put"
15+
16+
17+
@unique
18+
class TokenType(Enum):
19+
bugout = "bugout"
20+
slack = "slack"
21+
github = "github"
22+
23+
24+
class BugoutUser(BaseModel):
25+
id: uuid.UUID
26+
username: str
27+
email: str
28+
normalized_email: str
29+
verified: bool
30+
autogenerated: bool
31+
created_at: datetime
32+
updated_at: datetime
33+
34+
35+
class BugoutGroup(BaseModel):
36+
id: uuid.UUID
37+
group_name: Optional[str]
38+
autogenerated: bool
39+
40+
41+
class BugoutJournal(BaseModel):
42+
id: uuid.UUID
43+
bugout_user_id: uuid.UUID
44+
holder_ids: List[uuid.UUID]
45+
name: str
46+
created_at: datetime
47+
updated_at: datetime
48+
49+
50+
class BugoutToken(BaseModel):
51+
id: uuid.UUID
52+
token_type: Optional[str]
53+
token_note: Optional[str]

bugout/group.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import logging
2+
from typing import Any, Dict, List, Optional, Tuple
3+
4+
from .calls import make_request, InvalidUrlSpec
5+
from .data import BugoutGroup, Method
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class GroupNotFound(Exception):
11+
"""
12+
Raised on actions that involve group which are not present in the database.
13+
"""
14+
15+
16+
class Group:
17+
"""
18+
Represent a group from Bugout.
19+
"""
20+
21+
def __init__(self, url: Optional[str] = None) -> None:
22+
if url is None:
23+
raise InvalidUrlSpec("Invalid brood url specified")
24+
self.url = url
25+
26+
def _call(self, method: Method, path: str, **kwargs):
27+
url = f"{self.url.rstrip('/')}/{path.rstrip('/')}"
28+
result = make_request(method=method, url=url, **kwargs)
29+
return result
30+
31+
def get_group(self, group_id: str, token: str) -> BugoutGroup:
32+
get_group_path = f"groups/{group_id}"
33+
headers = {
34+
"Authorization": f"Bearer {token}",
35+
}
36+
result = self._call(method=Method.get, path=get_group_path, headers=headers)
37+
return BugoutGroup(
38+
id=result.get("id"),
39+
name=result.get("name"),
40+
autogenerated=result.get("autogenerated"),
41+
)

bugout/journal.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import logging
2+
from typing import Any, Dict, List, Optional, Tuple
3+
4+
from .calls import make_request, InvalidUrlSpec
5+
from .data import BugoutJournal, Method
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class JournalNotFound(Exception):
11+
"""
12+
Raised on actions that involve journal which are not present in the database.
13+
"""
14+
15+
16+
class Journal:
17+
"""
18+
Represent a journal from Bugout.
19+
"""
20+
21+
def __init__(self, url: Optional[str] = None) -> None:
22+
if url is None:
23+
raise InvalidUrlSpec("Invalid spire url specified")
24+
self.url = url
25+
26+
def _call(self, method: Method, path: str, **kwargs):
27+
url = f"{self.url.rstrip('/')}/{path.rstrip('/')}"
28+
result = make_request(method=method, url=url, **kwargs)
29+
return result
30+
31+
def get_journal(self, journal_id: str, token: str) -> BugoutJournal:
32+
get_group_path = f"journals/{journal_id}"
33+
headers = {
34+
"Authorization": f"Bearer {token}",
35+
}
36+
result = self._call(method=Method.get, path=get_group_path, headers=headers)
37+
return BugoutJournal(
38+
id=result.get("id"),
39+
bugout_user_id=result.get("bugout_user_id"),
40+
holder_ids=result.get("holder_ids"),
41+
name=result.get("name"),
42+
created_at=result.get("created_at"),
43+
updated_at=result.get("updated_at"),
44+
)

0 commit comments

Comments
 (0)