Skip to content

Commit 6b82d45

Browse files
authored
NeptuneML integration (#48)
- Integration with NeptuneML feature set in AWS Neptune - Add helper library to perform Sigv4 signing for `%neptune_ml export ...`, we will move our other signing at a later date. - Swap how credentials are obtained for `ROLE` iam credentials provider such that it uses a botocore session now instead of calling the ec2 metadata service. This should make the module more usable outside of Sagemaker. New Line magics: - `%neptune_ml export status` - `%neptune_ml dataprocessing start` - `%neptune_ml dataprocessing status` - `%neptune_ml training start` - `%neptune_ml training status` - `%neptune_ml endpoint create` - `%neptune_ml endpoint status` New Cell magics: - `%%neptune_ml export start` - `%%neptune_ml dataprocessing start` - `%%neptune_ml training start` - `%%neptune_ml endpoint create` NOTE: If a cell magic is used, its line inputs for specifying parts of the command will be ignore such as `--job-id` as a line-param. Inject variable as cell input: Currently this will only work for our new cell magic commands details above. You can now specify a variable to use as the cell input received by our `neptune_ml` magics using the syntax ${var_name}. For example... ``` # in one notebook cell: foo = {'foo', 'bar'} # in another notebook cell: %%neptune_ml export start ${foo} ``` NOTE: The above will only work if it is the sole content of the cell body. You cannot inline multiple variables at this time.
1 parent 15ae7c9 commit 6b82d45

File tree

22 files changed

+629
-31
lines changed

22 files changed

+629
-31
lines changed

.github/workflows/integration.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ jobs:
3838
- uses: actions/checkout@v2
3939
- name: Install dependencies
4040
run: |
41-
python -m pip install --upgrade pip
42-
pip install flake8 pytest
41+
pip install pytest
4342
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
4443
- name: Install
4544
run: |
@@ -75,7 +74,6 @@ jobs:
7574
- name: Install dependencies
7675
run: |
7776
python -m pip install --upgrade pip
78-
pip install flake8 pytest
7977
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
8078
- name: Install
8179
run: |

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ include webpack.config.js
44

55
# Javascript files
66
recursive-include src/graph_notebook/widgets *
7-
prune **/node_modules
7+
prune src/graph_notebook/widgets/node_modules
88
prune coverage
99

1010
# Patterns to exclude from any directory

THIRD_PARTY_LICENSES.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,6 +2591,36 @@ SOFTWARE.
25912591

25922592
------
25932593

2594+
** requests-aws4auth 1.0.1; version 1.0.1 --
2595+
https://pypi.org/project/requests-aws4auth/
2596+
The MIT License (MIT)
2597+
2598+
Copyright (c) 2015 Sam Washington
2599+
2600+
The MIT License (MIT)
2601+
2602+
Copyright (c) 2015 Sam Washington
2603+
2604+
Permission is hereby granted, free of charge, to any person obtaining a copy
2605+
of this software and associated documentation files (the "Software"), to deal
2606+
in the Software without restriction, including without limitation the rights
2607+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2608+
copies of the Software, and to permit persons to whom the Software is
2609+
furnished to do so, subject to the following conditions:
2610+
2611+
The above copyright notice and this permission notice shall be included in all
2612+
copies or substantial portions of the Software.
2613+
2614+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2615+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2616+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2617+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2618+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2619+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2620+
SOFTWARE.
2621+
2622+
------
2623+
25942624
** @types/webpack-env; version 1.15.2 --
25952625
https://github.com/DefinitelyTyped/DefinitelyTyped
25962626
Copyright (c) Microsoft Corporation. All rights reserved.

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def get_version():
7676
'notebook',
7777
'jupyter-contrib-nbextensions',
7878
'widgetsnbextension',
79-
'jupyter>=1.0.0'
79+
'jupyter>=1.0.0',
80+
'requests-aws4auth==1.0.1',
81+
'botocore>=1.19.37'
8082
],
8183
package_data={
8284
'graph_notebook': ['graph_notebook/widgets/nbextensions/static/*.js',

setupbase.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,14 @@ def run(self):
382382
if should_build:
383383
run(npm_cmd + ['run', build_cmd], cwd=node_package)
384384

385+
# ensure that __init__.py files are added to generated directories, otherwise it will not be packaged with
386+
# package distribution to pypi
387+
dirs_from_node_path = ['nbextension', pjoin('nbextension', 'static'), 'lib', 'labextension']
388+
for init_path in dirs_from_node_path:
389+
full_path = pjoin(node_package, init_path, '__init__.py')
390+
with open(full_path, 'w+'):
391+
pass
392+
385393
return NPM
386394

387395

src/graph_notebook/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
SPDX-License-Identifier: Apache-2.0
44
"""
55

6-
__version__ = '2.0.2'
6+
__version__ = '2.0.3'

src/graph_notebook/authentication/iam_credentials_provider/ec2_metadata_credentials_provider.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
SPDX-License-Identifier: Apache-2.0
44
"""
55

6+
import botocore.session
67
import requests
78

9+
810
from graph_notebook.authentication.iam_credentials_provider.credentials_provider import CredentialsProviderBase, \
911
Credentials
1012

1113
region_url = 'http://169.254.169.254/latest/meta-data/placement/availability-zone'
12-
iam_url = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/neptune-db'
1314

1415

1516
class MetadataCredentialsProvider(CredentialsProviderBase):
@@ -20,10 +21,6 @@ def __init__(self):
2021
self.region = region
2122

2223
def get_iam_credentials(self) -> Credentials:
23-
res = requests.get(iam_url)
24-
if res.status_code != 200:
25-
raise Exception(f'unable to get iam credentials {res.content}')
26-
27-
js = res.json()
28-
creds = Credentials(key=js['AccessKeyId'], secret=js['SecretAccessKey'], token=js['Token'], region=self.region)
29-
return creds
24+
session = botocore.session.get_session()
25+
creds = session.get_credentials()
26+
return Credentials(key=creds.access_key, secret=creds.secret_key, token=creds.token, region=self.region)

src/graph_notebook/authentication/iam_credentials_provider/env_credentials_provider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def load_iam_credentials(self):
2828
self.loaded = True
2929
return
3030

31-
def get_iam_credentials(self) -> Credentials:
31+
def get_iam_credentials(self, service=None) -> Credentials:
3232
if not self.loaded:
3333
self.load_iam_credentials()
3434

src/graph_notebook/authentication/iam_headers.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import datetime
77
import hashlib
88
import hmac
9+
import json
910
import logging
1011
import urllib
1112

12-
1313
logging.basicConfig()
1414
logger = logging.getLogger("graph_magic")
1515

@@ -69,6 +69,19 @@ def get_canonical_uri_and_payload(query_type, query):
6969
elif query_type == "system":
7070
canonical_uri = "/system/"
7171
payload = query
72+
73+
elif query_type.startswith("ml"):
74+
canonical_uri = f'/{query_type}'
75+
payload = query
76+
77+
elif query_type.startswith("ml/dataprocessing"):
78+
canonical_uri = f'/{query_type}'
79+
payload = query
80+
81+
elif query_type.startswith("ml/endpoints"):
82+
canonical_uri = f'/{query_type}'
83+
payload = query
84+
7285
else:
7386
raise ValueError('query_type %s is not valid' % query_type)
7487

@@ -85,7 +98,8 @@ def normalize_query_string(query):
8598
return normalized
8699

87100

88-
def make_signed_request(method, query_type, query, host, port, signing_access_key, signing_secret, signing_region, use_ssl=False, signing_token='', additional_headers=None):
101+
def make_signed_request(method, query_type, query, host, port, signing_access_key, signing_secret, signing_region,
102+
use_ssl=False, signing_token='', additional_headers=None):
89103
if additional_headers is None:
90104
additional_headers = []
91105

@@ -103,8 +117,11 @@ def make_signed_request(method, query_type, query, host, port, signing_access_ke
103117
# get canonical_uri and payload
104118
canonical_uri, payload = get_canonical_uri_and_payload(query_type, query)
105119

106-
request_parameters = urllib.parse.urlencode(payload, quote_via=urllib.parse.quote)
107-
request_parameters = request_parameters.replace('%27', '%22')
120+
if 'content-type' in additional_headers and additional_headers['content-type'] == 'application/json':
121+
request_parameters = payload if type(payload) is str else json.dumps(payload)
122+
else:
123+
request_parameters = urllib.parse.urlencode(payload, quote_via=urllib.parse.quote)
124+
request_parameters = request_parameters.replace('%27', '%22')
108125
t = datetime.datetime.utcnow()
109126
amz_date = t.strftime('%Y%m%dT%H%M%SZ')
110127
date_stamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope

src/graph_notebook/magics/graph_magic.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import graph_notebook
2424
from graph_notebook.configuration.generate_config import generate_default_config, DEFAULT_CONFIG_LOCATION
2525
from graph_notebook.decorators.decorators import display_exceptions
26+
from graph_notebook.magics.ml import neptune_ml_magic_handler, generate_neptune_ml_parser
2627
from graph_notebook.network import SPARQLNetwork
2728
from graph_notebook.network.gremlin.GremlinNetwork import parse_pattern_list_str, GremlinNetwork
2829
from graph_notebook.sparql.table import get_rows_and_columns
@@ -994,3 +995,20 @@ def graph_notebook_vis_options(self, line='', cell=''):
994995
else:
995996
options_dict = json.loads(cell)
996997
self.graph_notebook_vis_options = vis_options_merge(self.graph_notebook_vis_options, options_dict)
998+
999+
@line_cell_magic
1000+
@display_exceptions
1001+
@needs_local_scope
1002+
def neptune_ml(self, line, cell='', local_ns: dict = None):
1003+
parser = generate_neptune_ml_parser()
1004+
args = parser.parse_args(line.split())
1005+
logger.info(f'received call to neptune_ml with details: {args.__dict__}, cell={cell}, local_ns={local_ns}')
1006+
request_generator = create_request_generator(self.graph_notebook_config.auth_mode,
1007+
self.graph_notebook_config.iam_credentials_provider_type)
1008+
main_output = widgets.Output()
1009+
display(main_output)
1010+
res = neptune_ml_magic_handler(args, request_generator, self.graph_notebook_config, main_output, cell, local_ns)
1011+
message = json.dumps(res, indent=2) if type(res) is dict else res
1012+
store_to_ns(args.store_to, res, local_ns)
1013+
with main_output:
1014+
print(message)

0 commit comments

Comments
 (0)