Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A GraphQL Extension #2196

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ simple-websocket==0.8.1
protobuf==3.20.2
PyJWT==2.4.0
pytimeparse==1.1.8
# core-extension dependencies
cryptoadvance.spectrum==0.3.1
psycopg2-binary==2.9.5
typing_extensions==4.0.0
strawberry-graphql==0.155.2
# Extensions
cryptoadvance-liquidissuer==0.2.4
specterext-exfund==0.1.7
Expand Down
1 change: 1 addition & 0 deletions src/cryptoadvance/specter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class BaseConfig(object):
"cryptoadvance.specterext.electrum.service",
"cryptoadvance.specterext.spectrum.service",
"cryptoadvance.specterext.stacktrack.service",
"cryptoadvance.specterext.graphql.service",
]

# This is just a placeholder in order to be aware that you cannot set this
Expand Down
8 changes: 5 additions & 3 deletions src/cryptoadvance/specter/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from cryptoadvance.specter.addresslist import Address
from cryptoadvance.specter.services import callbacks
from ..specter_error import SpecterInternalException


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -69,9 +70,10 @@ def _storage_manager(cls):
)

def callback(self, callback_id, *argv, **kwargv):
if callback_id == callbacks.after_serverpy_init_app:
if hasattr(self, "callback_after_serverpy_init_app"):
self.callback_after_serverpy_init_app(kwargv["scheduler"])
"""DEPRECATED"""
raise SpecterInternalException(
"This style of callback is deprecated. Please revisit the extension which is using it."
)

@classmethod
def set_current_user_service_data(cls, service_data: dict):
Expand Down
5 changes: 5 additions & 0 deletions src/cryptoadvance/specterext/devhelp/callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from cryptoadvance.specter.services.callbacks import Callback


class my_callback(Callback):
id = "my_callback"
53 changes: 53 additions & 0 deletions src/cryptoadvance/specterext/devhelp/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
{
"readlater": "no",
"annotations": [],
"tags": "no_tag",
"comments": [],
"shared": "yes",
"url": "http://a16z.com/2014/05/30/selling-saas-products-dont-sell-themselves/",
"desc": "\"Some people think the sales force’s job is to communicate value to customers.They’re wrong.\" @mdcranney : http://t.co/t146HVhuuT via @a16z",
"title": "If SaaS Products Sell Themselves, Why Do We Need Sales? | Andreessen Horowitz"
},
{
"readlater": "no",
"annotations": [],
"tags": "bitcoin,video,education",
"comments": [],
"shared": "yes",
"url": "https://www.youtube.com/watch?v=MhmxpfA7_Ag",
"desc": "",
"title": "Bitcoin Decrypted Part III: Social theory aspects - YouTube"
},
{
"readlater": "no",
"annotations": [],
"tags": "bitcoin,video,education",
"comments": [],
"shared": "yes",
"url": "https://www.youtube.com/watch?v=HvfO5u4ImAU",
"desc": "",
"title": "Bitcoin Decrypted Part II: Technical aspects (updated) - YouTube"
},
{
"readlater": "no",
"annotations": [],
"tags": "bitcoin,video,education",
"comments": [],
"shared": "yes",
"url": "https://www.youtube.com/watch?v=O4yrvRxFEWs",
"desc": "",
"title": "Bitcoin Decrypted Part I: Context and overview - YouTube"
},
{
"readlater": "no",
"annotations": [],
"tags": "no_tag",
"comments": [],
"shared": "yes",
"url": "http://t.co/Ub9XKrbInU",
"desc": "“You’ve been around long enough to know there won’t be any calm periods” @tomheon: Speeding Up Your Engineering Org http://t.co/Ub9XKrbInU",
"title": "“You’ve been around long enoug"
}
]

40 changes: 40 additions & 0 deletions src/cryptoadvance/specterext/devhelp/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import typing
import strawberry
from typing import List
import json
import os
import logging

logger = logging.getLogger(__name__)


@strawberry.type
class Bookmark:
url: str
desc: str
readlater: str
annotations: List[str]
tags: str
comments: List[str]
shared: str
title: str


def get_bookmarks() -> List[Bookmark]:
logger.info("CALLING get_bookmarks!!")
with open("src/cryptoadvance/specterext/devhelp/data.json") as json_file:
data = json.load(json_file)
listOfBookmarks = []
for item in data:
b = Bookmark(
url=item["url"],
desc=item["desc"],
readlater=item["readlater"],
annotations=[], # fixme
tags=item["tags"],
comments=item["comments"],
shared=item["shared"],
title=item["title"],
)
listOfBookmarks.append(b)
return listOfBookmarks
32 changes: 31 additions & 1 deletion src/cryptoadvance/specterext/devhelp/service.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import logging
import typing
from typing import List

import flask
import flask_login
import strawberry
from flask import current_app as app
from flask import render_template
from flask_login import current_user

from cryptoadvance.specter import util
from cryptoadvance.specter.services.service import Service, devstatus_alpha
from cryptoadvance.specter.specter_error import SpecterError
from cryptoadvance.specter.wallet import Wallet
from cryptoadvance.specter import util
from . import schema as devhelp_schema
from .callbacks import my_callback
from .console import Console
from .schema import get_bookmarks
import flask
import flask_login
from flask_login import current_user

logger = logging.getLogger(__name__)

Expand All @@ -22,6 +33,8 @@ class DevhelpService(Service):
has_blueprint = True
blueprint_module = "cryptoadvance.specterext.devhelp.controller"
devices = ["cryptoadvance.specterext.devhelp.devices.devhelpdevice"]
# Specifying the my_callback here:
callbacks = ["cryptoadvance.specterext.devhelp.callbacks"]
devstatus = devstatus_alpha
console = Console()
console.updateNamespace(
Expand All @@ -33,6 +46,23 @@ class DevhelpService(Service):
# ServiceEncryptedStorage field names for Swan
SPECTER_WALLET_ALIAS = "wallet"

def inform_world(self, msg="Hello World"):
"""Just a test method which you might want to call via the devhelper-console"""
# Calling the self-specified callback!
app.specter.service_manager.execute_ext_callbacks(my_callback, msg)

def callback_my_callback(self, msg):
"""Implementing the self declared callback-method
This is usually implemented in other extensions which depend on this one
"""
print(msg)

def callback_create_graphql_schema(self, field_list):
"""demoing the graphQL extension"""
# Add your fields to the Schema like this::
field_list.append(strawberry.field(name="bookmarks", resolver=get_bookmarks))
return field_list

@classmethod
def get_associated_wallet(cls) -> Wallet:
"""Get the Specter `Wallet` that is currently associated with this service"""
Expand Down
9 changes: 9 additions & 0 deletions src/cryptoadvance/specterext/graphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Inspired by strawberry and the small [POC](https://github.com/k9ert/nomstr)

# potential issues

There might be a dependency conflict:
```
typing_extensions<5.0.0,>=3.7.4 (from strawberry-graphql==0.155.2->-r requirements.in (line 35))
typing-extensions<4.0,>=3.7 (from hwi==2.1.1->-r requirements.in (line 11))
```
Empty file.
15 changes: 15 additions & 0 deletions src/cryptoadvance/specterext/graphql/callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from cryptoadvance.specter.services.callbacks import Callback


class create_graphql_schema(Callback):
"""
Gives you the ability to add to the GraphQL schema
example:
def callback_create_graphql_schema(self, field_list):
# Add your fields to the Schema like this::
field_list.append(strawberry.field(name="bookmarks", resolver=get_bookmarks))
return field_list
"""

id = "create_graphql_schema"
return_style = "middleware"
31 changes: 31 additions & 0 deletions src/cryptoadvance/specterext/graphql/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Here Configuration of your Extension takes place
"""

import os


def _get_bool_env_var(varname, default=None):

value = os.environ.get(varname, default)

if value is None:
return False
elif isinstance(value, str) and value.lower() == "false":
return False
elif bool(value) is False:
return False
else:
return bool(value)


class BaseConfig:
"""This is a extension-based Config which is used as Base"""

SPECTER_GRAPHQL_ACTIVE = _get_bool_env_var("SPECTER_GRAPHQL_ACTIVE", "True")


class ProductionConfig(BaseConfig):
"""This is a extension-based Config for Production"""

SPECTER_GRAPHQL_ACTIVE = _get_bool_env_var("SPECTER_GRAPHQL_ACTIVE", "False")
73 changes: 73 additions & 0 deletions src/cryptoadvance/specterext/graphql/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging
from flask import redirect, render_template, request, url_for, flash
from flask import current_app as app
from flask_login import login_required, current_user
from strawberry.flask.views import GraphQLView

from cryptoadvance.specter.specter import Specter
from cryptoadvance.specter.services.controller import user_secret_decrypted_required
from cryptoadvance.specter.user import User
from cryptoadvance.specter.wallet import Wallet
from .service import GraphqlService


logger = logging.getLogger(__name__)

graphql_endpoint = GraphqlService.blueprint


def ext() -> GraphqlService:
"""convenience for getting the extension-object"""
return app.specter.ext["graphql"]


def specter() -> Specter:
"""convenience for getting the specter-object"""
return app.specter


# This endpoint is added dynamically in service.py
# @graphql_endpoint.route("/graphql", methods=["GET", "POST"])
# @app.csrf.exempt
# def index():
# view_func = GraphQLView.as_view("graphql_view", schema=schema2)
# return view_func()


@graphql_endpoint.route("/")
@login_required
def index():
return render_template(
"graphql/index.jinja",
)


@graphql_endpoint.route("/transactions")
@login_required
def schema():
# The wallet currently configured for ongoing autowithdrawals

return render_template(
"graphql/schema.jinja",
)


@graphql_endpoint.route("/settings", methods=["GET"])
@login_required
def settings_get():

return render_template(
"graphql/settings.jinja",
)


@graphql_endpoint.route("/settings", methods=["POST"])
@login_required
def settings_post():
show_menu = request.form["show_menu"]
user = app.specter.user_manager.get_user()
if show_menu == "yes":
user.add_service(GraphqlService.id)
else:
user.remove_service(GraphqlService.id)
return redirect(url_for(f"{ GraphqlService.get_blueprint_name()}.settings_get"))
30 changes: 30 additions & 0 deletions src/cryptoadvance/specterext/graphql/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from flask import current_app as app
from cryptoadvance.specter.managers.user_manager import UserManager

import typing
import strawberry
from typing import List
import json
import os
import logging

logger = logging.getLogger(__name__)


@strawberry.type
class User:
username: str


def get_users() -> List[User]:
um: UserManager = app.specter.user_manager
um.users
listOfUsers = []
for item in um.users:
u = User(username=item.username)
listOfUsers.append(u)
return listOfUsers


def create_fields():
return [strawberry.field(name="users", resolver=get_users)]
Loading