1- import abc
21import http
32import json
43import logging
54from typing import Dict , List , Optional , Tuple , Union , Any
65
7- import nbformat
86import tornado
9- import traitlets
107import httpx
8+ import traitlets
119from jupyter_server .utils import url_path_join
1210
13- from ..log import get_logger
14- from ..base import DrivesConfig
11+ import obstore as obs
12+ from libcloud .storage .types import Provider
13+ from libcloud .storage .providers import get_driver
14+
15+ from .log import get_logger
16+ from .base import DrivesConfig
1517
1618import re
1719
18- class JupyterDrivesManager (abc . ABC ):
20+ class JupyterDrivesManager ():
1921 """
20- Abstract base class for jupyter -drives manager.
22+ Jupyter -drives manager class .
2123
2224 Args:
2325 config: Server extension configuration object
@@ -26,12 +28,12 @@ class JupyterDrivesManager(abc.ABC):
2628
2729 The manager will receive the global server configuration object;
2830 so it can add configuration parameters if needed.
29- It needs them to extract the ``DrivesConfig`` from it to pass it to this
30- parent class (see ``S3Manager`` for an example).
31+ It needs them to extract the ``DrivesConfig``.
3132 """
32- def __init__ (self , config : DrivesConfig ) -> None :
33- self ._config = config
33+ def __init__ (self , config : traitlets . config . Config ) -> None :
34+ self ._config = DrivesConfig ( config = config )
3435 self ._client = httpx .AsyncClient ()
36+ self ._content_managers = {}
3537
3638 @property
3739 def base_api_url (self ) -> str :
@@ -50,19 +52,56 @@ def per_page_argument(self) -> Optional[Tuple[str, int]]:
5052 [str, int]: (query argument name, value)
5153 None: the provider does not support pagination
5254 """
53- return None
55+ return ( "per_page" , 100 )
5456
55- @abc .abstractclassmethod
5657 async def list_drives (self ):
5758 """Get list of available drives.
5859
5960 Returns:
6061 List of available drives and their properties.
6162 """
62- raise NotImplementedError ()
63+ data = []
64+ if self ._config .access_key_id and self ._config .secret_access_key :
65+ if self ._config .provider == "s3" :
66+ S3Drive = get_driver (Provider .S3 )
67+ drives = [S3Drive (self ._config .access_key_id , self ._config .secret_access_key )]
68+
69+ elif self ._config .provider == 'gcs' :
70+ GCSDrive = get_driver (Provider .GOOGLE_STORAGE )
71+ drives = [GCSDrive (self ._config .access_key_id , self ._config .secret_access_key )] # verfiy credentials needed
72+
73+ else :
74+ raise tornado .web .HTTPError (
75+ status_code = httpx .codes .NOT_IMPLEMENTED ,
76+ reason = "Listing drives not supported for given provider." ,
77+ )
78+
79+ results = []
80+ for drive in drives :
81+ results += drive .list_containers ()
82+
83+ for result in results :
84+ data .append (
85+ {
86+ "name" : result .name ,
87+ "region" : self ._config .region_name if self ._config .region_name is not None else "eu-north-1" ,
88+ "creation_date" : result .extra ["creation_date" ],
89+ "mounted" : "true" if result .name not in self ._content_managers else "false" ,
90+ "provider" : self ._config .provider
91+ }
92+ )
93+ else :
94+ raise tornado .web .HTTPError (
95+ status_code = httpx .codes .BAD_REQUEST ,
96+ reason = "No credentials specified. Please set them in your user jupyter_server_config file." ,
97+ )
98+
99+ response = {
100+ "data" : data
101+ }
102+ return response
63103
64- @abc .abstractclassmethod
65- async def mount_drive (self , drive_name , ** kwargs ):
104+ async def mount_drive (self , drive_name , provider , region ):
66105 """Mount a drive.
67106
68107 Args:
@@ -71,46 +110,75 @@ async def mount_drive(self, drive_name, **kwargs):
71110 Returns:
72111 The content manager for the drive.
73112 """
74- raise NotImplementedError ()
113+ try :
114+ # check if content manager doesn't already exist
115+ if drive_name not in self ._content_managers or self ._content_managers [drive_name ] is None :
116+ if provider == 's3' :
117+ store = obs .store .S3Store .from_url ("s3://" + drive_name + "/" , config = {"aws_access_key_id" : self ._config .access_key_id , "aws_secret_access_key" : self ._config .secret_access_key , "aws_region" : region })
118+ elif provider == 'gcs' :
119+ store = obs .store .GCSStore .from_url ("gs://" + drive_name + "/" , config = {}) # add gcs config
120+ elif provider == 'http' :
121+ store = obs .store .HTTPStore .from_url (drive_name , client_options = {}) # add http client config
122+
123+ self ._content_managers [drive_name ] = store
124+
125+ else :
126+ raise tornado .web .HTTPError (
127+ status_code = httpx .codes .CONFLICT ,
128+ reason = "Drive already mounted."
129+ )
130+
131+ except Exception as e :
132+ raise tornado .web .HTTPError (
133+ status_code = httpx .codes .BAD_REQUEST ,
134+ reason = f"The following error occured when mouting the drive: { e } "
135+ )
136+
137+ return
75138
76- @abc .abstractclassmethod
77- async def unmount_drive (self , drive_name : str , ** kwargs ):
139+ async def unmount_drive (self , drive_name : str ):
78140 """Unmount a drive.
79141
80142 Args:
81143 drive_name: name of drive to unmount
82144 """
83- raise NotImplementedError ()
145+ if drive_name in self ._content_managers :
146+ self ._content_managers .pop (drive_name , None )
147+
148+ else :
149+ raise tornado .web .HTTPError (
150+ status_code = httpx .codes .NOT_FOUND ,
151+ reason = "Drive is not mounted or doesn't exist." ,
152+ )
153+
154+ return
84155
85- @abc .abstractclassmethod
86156 async def get_contents (self , drive_name , path , ** kwargs ):
87157 """Get contents of a file or directory.
88158
89159 Args:
90160 drive_name: name of drive to get the contents of
91161 path: path to file or directory
92162 """
93- raise NotImplementedError ( )
163+ print ( 'Get contents function called.' )
94164
95- @abc .abstractclassmethod
96165 async def new_file (self , drive_name , path , ** kwargs ):
97166 """Create a new file or directory at the given path.
98167
99168 Args:
100169 drive_name: name of drive where the new content is created
101170 path: path where new content should be created
102171 """
103- raise NotImplementedError ( )
172+ print ( 'New file function called.' )
104173
105- @abc .abstractclassmethod
106174 async def rename_file (self , drive_name , path , ** kwargs ):
107175 """Rename a file.
108176
109177 Args:
110178 drive_name: name of drive where file is located
111179 path: path of file
112180 """
113- raise NotImplementedError ( )
181+ print ( 'Rename file function called.' )
114182
115183 async def _call_provider (
116184 self ,
0 commit comments