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
11+ import obstore as obs
12+ from libcloud .storage .types import Provider
13+ from libcloud .storage .providers import get_driver
14+
1315from ..log import get_logger
1416from ..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,18 +52,47 @@ 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+ results = []
70+ for drive in drives :
71+ results += drive .list_containers ()
72+
73+ for result in results :
74+ data .append (
75+ {
76+ "name" : result .name ,
77+ "region" : result .driver .region ,
78+ "creation_date" : result .extra ["creation_date" ],
79+ "status" : "inactive" ,
80+ "provider" : "S3"
81+ }
82+ )
83+ response = {
84+ "data" : data ,
85+ "code" : 200
86+ }
87+ else :
88+ response = {"code" : 400 , "message" : "No credentials specified. Please set them in your user jupyter_server_config file." }
89+ raise tornado .web .HTTPError (
90+ status_code = httpx .codes .BAD_REQUEST ,
91+ reason = "No credentials specified. Please set them in your user jupyter_server_config file." ,
92+ )
93+
94+ return response
6395
64- @abc .abstractclassmethod
6596 async def mount_drive (self , drive_name , ** kwargs ):
6697 """Mount a drive.
6798
@@ -71,46 +102,71 @@ async def mount_drive(self, drive_name, **kwargs):
71102 Returns:
72103 The content manager for the drive.
73104 """
74- raise NotImplementedError ()
105+ try :
106+ # check if content manager didn't already exist
107+ if drive_name not in self ._content_managers or self ._content_managers [drive_name ] is None :
108+ if kwargs .provider == 's3' :
109+ 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" : kwargs .drive_region })
110+ elif kwargs .provider == 'gcs' :
111+ store = obs .store .GCSStore .from_url ("gs://" + drive_name + "/" , config = {}) # add gcs config
112+ elif kwargs .provider == 'http' :
113+ store = obs .store .HTTPStore .from_url (drive_name , client_options = {}) # add http client config
114+ else :
115+ raise ValueError (f"Provider not supported: { kwargs .provider } " )
116+
117+ self ._content_managers [drive_name ].store = store
118+ self ._content_managers [drive_name ].provider = kwargs .provider
119+
120+ return store
121+ # response = {
122+ # "content_manager": store,
123+ # "code": 201,
124+ # "message": "Drive succesfully mounted."
125+ # }
126+ # else:
127+ # response = {
128+ # "code": 409,
129+ # "message": "Drive already mounted."
130+ # }
131+ except Exception as e :
132+ raise ValueError ("The following error occured when mouting the drive: {e}" )
133+
134+ # return response
75135
76- @abc .abstractclassmethod
77136 async def unmount_drive (self , drive_name : str , ** kwargs ):
78137 """Unmount a drive.
79138
80139 Args:
81140 drive_name: name of drive to unmount
82141 """
83- raise NotImplementedError ( )
142+ print ( 'Drive unmount function called.' )
84143
85- @abc .abstractclassmethod
86144 async def get_contents (self , drive_name , path , ** kwargs ):
87145 """Get contents of a file or directory.
88146
89147 Args:
90148 drive_name: name of drive to get the contents of
91149 path: path to file or directory
92150 """
93- raise NotImplementedError ( )
151+ print ( 'Get contents function called.' )
94152
95- @abc .abstractclassmethod
96153 async def new_file (self , drive_name , path , ** kwargs ):
97154 """Create a new file or directory at the given path.
98155
99156 Args:
100157 drive_name: name of drive where the new content is created
101158 path: path where new content should be created
102159 """
103- raise NotImplementedError ( )
160+ print ( 'New file function called.' )
104161
105- @abc .abstractclassmethod
106162 async def rename_file (self , drive_name , path , ** kwargs ):
107163 """Rename a file.
108164
109165 Args:
110166 drive_name: name of drive where file is located
111167 path: path of file
112168 """
113- raise NotImplementedError ( )
169+ print ( 'Rename file function called.' )
114170
115171 async def _call_provider (
116172 self ,
0 commit comments