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

[FSTORE-1389] get_secrets_api and create_project should be accessible as hopsworks module functions #202

Merged
merged 5 commits into from
May 10, 2024
Merged
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
8 changes: 4 additions & 4 deletions auto_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
PAGES = {
"api/login.md": {
"login": ["hopsworks.login"],
"get_current_project": ["hopsworks.get_current_project"],
"fs_api": ["hopsworks.project.Project.get_feature_store"],
"mr_api": ["hopsworks.project.Project.get_model_registry"],
"ms_api": ["hopsworks.project.Project.get_model_serving"],
Expand All @@ -36,9 +37,7 @@
),
},
"api/projects.md": {
"project_create": ["hopsworks.connection.Connection.create_project"],
"project_get": ["hopsworks.connection.Connection.get_project"],
"project_get_all": ["hopsworks.connection.Connection.get_projects"],
"project_create": ["hopsworks.create_project"],
"project_properties": keras_autodoc.get_properties("hopsworks.project.Project"),
"project_methods": keras_autodoc.get_methods(
"hopsworks.project.Project", exclude=["from_response_json", "json"]
Expand Down Expand Up @@ -163,9 +162,10 @@
),
},
"api/secrets.md": {
"secret_api_handle": ["hopsworks.connection.Connection.get_secrets_api"],
"secret_api_handle": ["hopsworks.get_secrets_api"],
"secret_create": ["hopsworks.core.secret_api.SecretsApi.create_secret"],
"secret_get": ["hopsworks.core.secret_api.SecretsApi.get_secret"],
"secret_get_simplified": ["hopsworks.core.secret_api.SecretsApi.get"],
"secret_get_all": ["hopsworks.core.secret_api.SecretsApi.get_secrets"],
"secret_properties": keras_autodoc.get_properties("hopsworks.secret.Secret"),
"secret_methods": keras_autodoc.get_methods(
Expand Down
2 changes: 2 additions & 0 deletions docs/templates/api/login.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

{{login}}

{{get_current_project}}

## Feature Store API

{{fs_api}}
Expand Down
6 changes: 0 additions & 6 deletions docs/templates/api/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@

{{project_create}}

## Retrieval

{{project_get}}

{{project_get_all}}

## Properties

{{project_properties}}
Expand Down
2 changes: 2 additions & 0 deletions docs/templates/api/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

## Retrieval

{{secret_get_simplified}}

{{secret_get}}

{{secret_get_all}}
Expand Down
165 changes: 147 additions & 18 deletions python/hopsworks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from hopsworks import client, constants, project, version
from hopsworks.client.exceptions import ProjectException, RestAPIError
from hopsworks.connection import Connection
from hopsworks.core import project_api, secret_api
from hopsworks.decorators import NoHopsworksConnectionError


# Needs to run before import of hsml and hsfs
Expand All @@ -42,6 +44,8 @@
_hw_connection = Connection.connection

_connected_project = None
_secrets_api = None
_project_api = None


def hw_formatwarning(message, category, filename, lineno, line=None):
Expand All @@ -68,21 +72,27 @@ def login(
) -> project.Project:
"""Connect to [Serverless Hopsworks](https://app.hopsworks.ai) by calling the `hopsworks.login()` function with no arguments.

```python
!!! example "Connect to Serverless"
```python

project = hopsworks.login()
import hopsworks

```
project = hopsworks.login()

```

Alternatively, connect to your own Hopsworks installation by specifying the host, port and api key.

```python
!!! example "Connect to your Hopsworks cluster"
```python

import hopsworks

project = hopsworks.login(host="my.hopsworks.server",
port=8181,
api_key_value="DKN8DndwaAjdf98FFNSxwdVKx")
project = hopsworks.login(host="my.hopsworks.server",
port=8181,
api_key_value="DKN8DndwaAjdf98FFNSxwdVKx")

```
```

In addition to setting function arguments directly, `hopsworks.login()` also reads the environment variables:
HOPSWORKS_HOST, HOPSWORKS_PORT, HOPSWORKS_PROJECT and HOPSWORKS_API_KEY.
Expand All @@ -97,7 +107,7 @@ def login(
api_key_value: Value of the Api Key
api_key_file: Path to file wih Api Key
# Returns
`Project`: The Project object
`Project`: The Project object to perform operations on
# Raises
`RestAPIError`: If unable to connect to Hopsworks
"""
Expand All @@ -113,6 +123,7 @@ def login(
if "REST_ENDPOINT" in os.environ:
_hw_connection = _hw_connection()
_connected_project = _hw_connection.get_project()
_initialize_module_apis()
print("\nLogged in to project, explore it here " + _connected_project.get_url())
return _connected_project

Expand Down Expand Up @@ -140,6 +151,8 @@ def login(
elif host is None: # Always do a fallback to Serverless Hopsworks if not defined
host = constants.HOSTS.APP_HOST

is_app = host == constants.HOSTS.APP_HOST

# If port same as default, get HOPSWORKS_HOST environment variable
if port == 443 and "HOPSWORKS_PORT" in os.environ:
port = os.environ["HOPSWORKS_PORT"]
Expand All @@ -166,23 +179,24 @@ def login(
"Could not find api key file on path: {}".format(api_key_file)
)
# If user connected to Serverless Hopsworks, and the cached .hw_api_key exists, then use it.
elif os.path.exists(api_key_path) and host == constants.HOSTS.APP_HOST:
elif os.path.exists(api_key_path) and is_app:
try:
_hw_connection = _hw_connection(
host=host, port=port, api_key_file=api_key_path
)
_connected_project = _prompt_project(_hw_connection, project)
_connected_project = _prompt_project(_hw_connection, project, is_app)
print(
"\nLogged in to project, explore it here "
+ _connected_project.get_url()
)
_initialize_module_apis()
return _connected_project
except RestAPIError:
logout()
# API Key may be invalid, have the user supply it again
os.remove(api_key_path)

if api_key is None and host == constants.HOSTS.APP_HOST:
if api_key is None and is_app:
print(
"Copy your Api Key (first register/login): https://c.app.hopsworks.ai/account/api/generated"
)
Expand All @@ -198,12 +212,19 @@ def login(

try:
_hw_connection = _hw_connection(host=host, port=port, api_key_value=api_key)
_connected_project = _prompt_project(_hw_connection, project)
_connected_project = _prompt_project(_hw_connection, project, is_app)
except RestAPIError as e:
logout()
raise e

print("\nLogged in to project, explore it here " + _connected_project.get_url())
if _connected_project is None:
print(
"Could not find any project, use hopsworks.create_project('my_project') to create one"
)
else:
print("\nLogged in to project, explore it here " + _connected_project.get_url())

_initialize_module_apis()
return _connected_project


Expand Down Expand Up @@ -245,11 +266,14 @@ def _get_cached_api_key_path():
return api_key_path


def _prompt_project(valid_connection, project):
def _prompt_project(valid_connection, project, is_app):
saas_projects = valid_connection.get_projects()
if project is None:
if len(saas_projects) == 0:
raise ProjectException("Could not find any project")
if is_app:
raise ProjectException("Could not find any project")
else:
return None
elif len(saas_projects) == 1:
return saas_projects[0]
else:
Expand All @@ -258,7 +282,9 @@ def _prompt_project(valid_connection, project):
for index in range(len(saas_projects)):
print("\t (" + str(index + 1) + ") " + saas_projects[index].name)
while True:
project_index = input("\nEnter project to access: ")
project_index = input(
"\nEnter number corresponding to the project to use: "
)
# Handle invalid input type
try:
project_index = int(project_index)
Expand All @@ -285,8 +311,111 @@ def _prompt_project(valid_connection, project):


def logout():
"""Cleans up and closes the connection for the hopsworks, hsfs and hsml libraries."""
global _hw_connection
if isinstance(_hw_connection, Connection):
global _project_api
global _secrets_api

if _is_connection_active():
_hw_connection.close()

client.stop()
_project_api = None
_secrets_api = None
_hw_connection = Connection.connection


def _is_connection_active():
global _hw_connection
return isinstance(_hw_connection, Connection)


def get_current_project() -> project.Project:
"""Get a reference to the current logged in project.

!!! example "Example for getting the project reference"
```python

import hopsworks

hopsworks.login()

project = hopsworks.get_current_project()

```

# Returns
`Project`. The Project object to perform operations on
"""
global _connected_project
if _connected_project is None:
raise ProjectException("No project is set for this session")
return _connected_project


def _initialize_module_apis():
global _project_api
global _secrets_api
_project_api = project_api.ProjectApi()
_secrets_api = secret_api.SecretsApi()


def create_project(name: str, description: str = None, feature_store_topic: str = None):
"""Create a new project.

!!! warning "Not supported"
This is not supported if you are connected to [Serverless Hopsworks](https://app.hopsworks.ai)

!!! example "Example for creating a new project"
```python

import hopsworks

hopsworks.login(...)

hopsworks.create_project("my_project", description="An example Hopsworks project")

```
# Arguments
name: The name of the project.
description: optional description of the project
feature_store_topic: optional feature store topic name

# Returns
`Project`. The Project object to perform operations on
"""
global _hw_connection
global _connected_project

if not _is_connection_active():
raise NoHopsworksConnectionError()

new_project = _hw_connection._project_api._create_project(
name, description, feature_store_topic
)
if _connected_project is None:
_connected_project = new_project
print(
"Setting {} as the current project, a reference can be retrieved by calling hopsworks.get_current_project()".format(
_connected_project.name
)
)
return _connected_project
else:
print(
"You are already using the project {}, to access the new project use hopsworks.login(..., project='{}')".format(
_connected_project.name, new_project.name
)
)


def get_secrets_api():
"""Get the secrets api.

# Returns
`SecretsApi`: The Secrets Api handle
"""
global _secrets_api
if not _is_connection_active():
raise NoHopsworksConnectionError()
return _secrets_api
3 changes: 1 addition & 2 deletions python/hopsworks/core/opensearch_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
#

from furl import furl

from hopsworks import client, constants
from hopsworks.core import variable_api
from hopsworks.client.exceptions import OpenSearchException
from hopsworks.core import variable_api


class OpenSearchApi:
Expand Down
Loading