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

staging: run recipes from firebase #179

Merged
merged 45 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
94ec7cd
[wip] prep recipe data for packing
rugeli Jun 14, 2023
2968f38
get creds from local file
meganrm Jun 15, 2023
6014501
save firebase creds to a .creds file
meganrm Jun 15, 2023
fb441f0
remove cred arg
rugeli Jun 20, 2023
0481d25
check for already handled values in remote recipes
rugeli Jun 20, 2023
b5b38c9
can pack one sphere
rugeli Jun 26, 2023
e921c35
* adding username to .creds
mogres Jul 5, 2023
0a3afff
move `write_username_to_creds`
mogres Jul 5, 2023
2dbfaaf
download recipe testing
rugeli Jul 10, 2023
b419a57
edit comment
rugeli Jul 10, 2023
f77163d
Merge branch 'feature/run-recipes-from-firebase' of https://github.co…
rugeli Jul 10, 2023
a0cd352
code refactor
rugeli Jul 10, 2023
bafba75
lint
rugeli Jul 10, 2023
a928c1d
format tests
rugeli Jul 13, 2023
cc0e19a
Merge branch 'main' of https://github.com/mesoscope/cellpack into fea…
rugeli Aug 18, 2023
1a8b715
add prep_db_doc
rugeli Aug 21, 2023
6cad8ee
changed class name in DBRecipeHandler
rugeli Aug 23, 2023
28af176
fix lint and test errors
rugeli Aug 23, 2023
68660b3
initialize firebase handler only once
rugeli Aug 25, 2023
14b3876
refactor message
rugeli Aug 25, 2023
70e1b12
add remote db options in `pack`
rugeli Aug 28, 2023
09fcbef
remove a print statement
rugeli Aug 28, 2023
f864fc6
rename and reorg DB handler
rugeli Sep 5, 2023
64dc318
fix tests
rugeli Sep 5, 2023
7672a93
move database_ids enum to interface_objects
rugeli Sep 6, 2023
ad77057
remove db_handler in pack and recipe_loader
rugeli Sep 6, 2023
f4c78ae
send db_handler in to autopack
rugeli Sep 6, 2023
1908692
rename functions
rugeli Sep 6, 2023
88abfbe
integrate DATABASE_NAMES into interface_objects
rugeli Sep 7, 2023
dfe19c8
lint
rugeli Sep 7, 2023
7209086
Merge branch 'main' of https://github.com/mesoscope/cellpack into fea…
rugeli Sep 27, 2023
cc97646
Feature/run inherited objects (#198)
rugeli Oct 12, 2023
7e2c633
Merge branch 'main' of https://github.com/mesoscope/cellpack into fea…
rugeli Oct 17, 2023
0d577fe
Merge branch 'feature/run-recipes-from-firebase' of https://github.co…
rugeli Oct 17, 2023
44c3cb2
formatting
rugeli Oct 17, 2023
7c0aae3
testing and refactor
rugeli Oct 23, 2023
1f5a0d8
Merge branch 'main' of https://github.com/mesoscope/cellpack into fea…
rugeli Oct 23, 2023
6f01cfd
Feature/save metadata to firebase (#206)
rugeli Nov 6, 2023
3e691a2
refactor
rugeli Nov 10, 2023
7364a86
Update .gitignore
rugeli Dec 12, 2023
c4b9786
add file existence check
rugeli Dec 12, 2023
4e392ea
Merge branch 'feature/run-recipes-from-firebase' of https://github.co…
rugeli Dec 12, 2023
55a313b
refactor is_nested_list method
rugeli Dec 12, 2023
74c43b3
revert write_json_file
rugeli Dec 13, 2023
93787fc
formatting
rugeli Dec 13, 2023
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
19 changes: 16 additions & 3 deletions cellpack/autopack/AWSHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class AWSHandler(object):
Handles all the AWS S3 operations
"""

# class attributes
_session_created = False
_s3_client = None

def __init__(
self,
bucket_name,
Expand All @@ -18,12 +22,21 @@ def __init__(
):
self.bucket_name = bucket_name
self.folder_name = sub_folder_name
session = boto3.Session()
self.s3_client = session.client(
# Create a session if one does not exist
if not AWSHandler._session_created:
self._create_session(region_name)
AWSHandler._session_created = True
else:
# use the existing session
self.s3_client = AWSHandler._s3_client

def _create_session(self, region_name):
AWSHandler._s3_client = boto3.client(
"s3",
endpoint_url=f"https://s3.{region_name}.amazonaws.com",
region_name=region_name,
)
self.s3_client = AWSHandler._s3_client

def get_aws_object_key(self, object_name):
if self.folder_name is not None:
Expand Down Expand Up @@ -82,4 +95,4 @@ def save_file(self, file_path):
"""
file_name = self.upload_file(file_path)
if file_name:
return self.create_presigned_url(file_name)
return file_name, self.create_presigned_url(file_name)
284 changes: 227 additions & 57 deletions cellpack/autopack/DBRecipeHandler.py

Large diffs are not rendered by default.

137 changes: 103 additions & 34 deletions cellpack/autopack/FirebaseHandler.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
import ast
import firebase_admin
from firebase_admin import credentials, firestore
from google.cloud.exceptions import NotFound
from cellpack.autopack.loaders.utils import read_json_file, write_json_file


class FirebaseHandler(object):
"""
Retrieve data and perform common tasks when working with firebase.
"""

def __init__(self, cred_path):
login = credentials.Certificate(cred_path)
firebase_admin.initialize_app(login)
self.db = firestore.client()
# use class attributes to maintain a consistent state across all instances
_initialized = False
_db = None

def __init__(self):
# check if firebase is already initialized
if not FirebaseHandler._initialized:
cred_path = FirebaseHandler.get_creds()
login = credentials.Certificate(cred_path)
firebase_admin.initialize_app(login)
FirebaseHandler._initialized = True
FirebaseHandler._db = firestore.client()

self.db = FirebaseHandler._db
self.name = "firebase"

# common utility methods
@staticmethod
def doc_to_dict(doc):
return doc.to_dict()

def db_name(self):
return self.name

@staticmethod
def doc_id(doc):
return doc.id
Expand All @@ -28,6 +39,10 @@ def doc_id(doc):
def create_path(collection, doc_id):
return f"firebase:{collection}/{doc_id}"

@staticmethod
def create_timestamp():
return firestore.SERVER_TIMESTAMP

@staticmethod
def get_path_from_ref(doc):
return doc.path
Expand All @@ -40,34 +55,51 @@ def get_collection_id_from_path(path):
id = components[1]
return collection, id

@staticmethod
def update_reference_on_doc(doc_ref, index, new_item_ref):
doc_ref.update({index: new_item_ref})
# Create methods
def set_doc(self, collection, id, data):
doc, doc_ref = self.get_doc_by_id(collection, id)
if not doc:
doc_ref = self.db.collection(collection).document(id)
doc_ref.set(data)
print(f"successfully uploaded to path: {doc_ref.path}")
return doc_ref
else:
print(
f"ERROR: {doc_ref.path} already exists. If uploading new data, provide a unique recipe name."
)
return

def upload_doc(self, collection, data):
return self.db.collection(collection).add(data)

# Read methods
@staticmethod
def update_elements_in_array(doc_ref, index, new_item_ref, remove_item):
doc_ref.update({index: firestore.ArrayRemove([remove_item])})
doc_ref.update({index: firestore.ArrayUnion([new_item_ref])})
def get_creds():
creds = read_json_file("./.creds")
if creds is None or "firebase" not in creds:
creds = FirebaseHandler.write_creds_path()
return creds["firebase"]

@staticmethod
def is_reference(path):
if not isinstance(path, str):
return False
if path is None:
return False
if path.startswith("firebase:"):
return True
return False
def get_username():
creds = read_json_file("./.creds")
try:
return creds["username"]
except KeyError:
raise ValueError("No username found in .creds file")

def db_name(self):
return self.name

def get_doc_by_name(self, collection, name):
db = self.db
data_ref = db.collection(collection)
docs = data_ref.where("name", "==", name).get() # docs is an array
return docs

# `doc` is a DocumentSnapshot object
# `doc_ref` is a DocumentReference object to perform operations on the doc
def get_doc_by_id(self, collection, id):
# `doc` is a DocumentSnapshot object
# `doc_ref` is a DocumentReference object to perform operations on the doc
doc_ref = self.db.collection(collection).document(id)
doc = doc_ref.get()
if doc.exists:
Expand All @@ -79,19 +111,56 @@ def get_doc_by_ref(self, path):
collection, id = FirebaseHandler.get_collection_id_from_path(path)
return self.get_doc_by_id(collection, id)

def set_doc(self, collection, id, data):
doc, doc_ref = self.get_doc_by_id(collection, id)
if not doc:
doc_ref = self.db.collection(collection).document(id)
doc_ref.set(data)
print(f"successfully uploaded to path: {doc_ref.path}")
return doc_ref
# Update methods
def update_doc(self, collection, id, data):
doc_ref = self.db.collection(collection).document(id)
doc_ref.update(data)
print(f"successfully updated to path: {doc_ref.path}")
return doc_ref

@staticmethod
def update_reference_on_doc(doc_ref, index, new_item_ref):
doc_ref.update({index: new_item_ref})

@staticmethod
def update_elements_in_array(doc_ref, index, new_item_ref, remove_item):
doc_ref.update({index: firestore.ArrayRemove([remove_item])})
doc_ref.update({index: firestore.ArrayUnion([new_item_ref])})

def update_or_create(self, collection, id, data):
"""
If the input id exists, update the doc. If not, create a new file.
"""
try:
self.update_doc(collection, id, data)
except NotFound:
self.set_doc(collection, id, data)

# other utils
@staticmethod
def write_creds_path():
path = ast.literal_eval(input("provide path to firebase credentials: "))
data = read_json_file(path)
if data is None:
raise ValueError("The path to your credentials doesn't exist")
firebase_cred = {"firebase": data}
creds = read_json_file("./.creds")
if creds is None:
write_json_file("./.creds", firebase_cred)
else:
print(f"ERROR, already data at this path:{collection}/{id}")
return
creds["firebase"] = data
write_json_file("./.creds", creds)
return firebase_cred

def upload_doc(self, collection, data):
return self.db.collection(collection).add(data)
@staticmethod
def is_reference(path):
if not isinstance(path, str):
return False
if path is None:
return False
if path.startswith("firebase:"):
return True
return False

@staticmethod
def is_firebase_obj(obj):
Expand Down
44 changes: 30 additions & 14 deletions cellpack/autopack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@
import re
import shutil
from os import path, environ
import getpass
from pathlib import Path
import urllib.request as urllib
from collections import OrderedDict
import ssl
import json
from cellpack.autopack.DBRecipeHandler import DBRecipeLoader
from cellpack.autopack.interface_objects.database_ids import DATABASE_IDS

from cellpack.autopack.interface_objects.meta_enum import MetaEnum
from cellpack.autopack.loaders.utils import read_json_file, write_json_file


packageContainsVFCommands = 1
Expand Down Expand Up @@ -192,19 +195,15 @@ def checkPath():
autopackdir = pref_path["autopackdir"]


class DATABASE_NAME(MetaEnum):
GITHUB = "github:"
FIREBASE = "firebase:"


REPLACE_PATH = {
"autoPACKserver": autoPACKserver,
"autopackdir": autopackdir,
"autopackdata": appdata,
DATABASE_NAME.GITHUB: autoPACKserver,
DATABASE_NAME.FIREBASE: None,
f"{DATABASE_IDS.GITHUB}:": autoPACKserver,
f"{DATABASE_IDS.FIREBASE}:": None,
}


global CURRENT_RECIPE_PATH
CURRENT_RECIPE_PATH = appdata
# we keep the file here, it come with the distribution
Expand Down Expand Up @@ -280,7 +279,7 @@ def is_remote_path(file_path):
"""
@param file_path: str
"""
for ele in DATABASE_NAME:
for ele in DATABASE_IDS.with_colon():
if ele in file_path:
return True

Expand Down Expand Up @@ -384,10 +383,16 @@ def read_text_file(filename, destination="", cache="collisionTrees", force=None)
def load_file(filename, destination="", cache="geometries", force=None):
if is_remote_path(filename):
database_name, file_path = convert_db_shortname_to_url(filename)
# command example: `pack -r firebase:recipes/[FIREBASE-RECIPE-ID] -c [CONFIG-FILE-PATH]`
if database_name == "firebase":
# TODO: read from firebase
# return data
pass
db = DATABASE_IDS.handlers().get(database_name)
db_handler = DBRecipeLoader(db)
recipe_id = file_path.split("/")[-1]
db_doc, _ = db_handler.collect_docs_by_id(
collection="recipes", id=recipe_id
)
downloaded_recipe_data = db_handler.prep_db_doc_for_download(db_doc)
return downloaded_recipe_data, database_name
else:
local_file_path = get_local_file_location(
file_path, destination=destination, cache=cache, force=force
Expand All @@ -396,7 +401,7 @@ def load_file(filename, destination="", cache="geometries", force=None):
local_file_path = get_local_file_location(
filename, destination=destination, cache=cache, force=force
)
return json.load(open(local_file_path, "r"))
return json.load(open(local_file_path, "r")), None


def fixPath(adict): # , k, v):
Expand Down Expand Up @@ -532,16 +537,27 @@ def clearCaches(*args):
print("problem cleaning ", cache_dir[k])


def write_username_to_creds():
username = getpass.getuser()
creds = read_json_file("./.creds")
if creds is None or "username" not in creds:
creds = {}
creds["username"] = username
write_json_file("./.creds", creds)


# we should read a file to fill the RECIPE Dictionary
# so we can add some and write/save setup
# afdir or user_pref

if checkAtstartup:
checkPath()
# updatePathJSON()
# checkRecipeAvailable()
log.info("path are updated ")

# write username to creds
write_username_to_creds()

log.info(f"currently number recipes is {len(RECIPES)}")
# check cache directory create if doesnt exit.abs//should be in user pref?
# ?
Expand Down
28 changes: 28 additions & 0 deletions cellpack/autopack/interface_objects/database_ids.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from .meta_enum import MetaEnum
from cellpack.autopack.AWSHandler import AWSHandler
from cellpack.autopack.FirebaseHandler import FirebaseHandler


class DATABASE_IDS(MetaEnum):
FIREBASE = "firebase"
GITHUB = "github"
AWS = "aws"

@classmethod
def with_colon(cls):
return [f"{ele}:" for ele in cls.values()]

@classmethod
def handlers(cls):
def create_aws_handler(bucket_name, sub_folder_name, region_name):
return AWSHandler(
bucket_name=bucket_name,
sub_folder_name=sub_folder_name,
region_name=region_name,
)

handlers_dict = {
cls.FIREBASE: FirebaseHandler(),
cls.AWS: create_aws_handler,
}
return handlers_dict
Loading
Loading