class StreamIEO(object): def __init__(self, logname): """ A class that individually threads the capture of the stdout stream and the stderr stream of a system command. The stdout/stderr are queued in the order they occur. As the que populates another thread, parse the que and prints to the screen using the LogIT class. """ self.io_q = Queue() self.process = None self.streamieolog = LogIt().default(logname="%s - streamieo" % logname, logfile=None) def streamer(self, cmd): self.process = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, encoding='utf-8') # Add the command line to the que self.io_q.put(("STDIN", cmd)) # Watch the standard output and add it to the que Thread(target=self._stream_watcher, name='stdout-watcher', args=('STDOUT', self.process.stdout)).start() # Watch the standard error and add it to the que Thread(target=self._stream_watcher, name='stderr-watcher', args=('STDERR', self.process.stderr)).start() # As items are added, print the stream. Thread(target=self._printer, name='_printer').start() def _stream_watcher(self, identifier, stream): # Watch the stream and add to the que dynamically # This runs in tandem with the printer. So as the stdout/stderr streams are queued here, # the que is parsed and printed in the printer function. for line in stream: self.io_q.put((identifier, line)) if not stream.closed: stream.close() def _printer(self): # Prints the que as it is populated with stdout/stderr dynamically. while True: try: # Block for 1 second. item = self.io_q.get(True, 1) except Empty: # No output in either streams for a second. Are we done? if self.process.poll() is not None: break else: identifier, line = item if identifier == "STDIN": self.streamieolog.warn("Command: " + line.strip()) elif identifier == "STDERR": self.streamieolog.error(line.strip()) elif identifier == "STDOUT": self.streamieolog.info(line.strip()) else: self.streamieolog.critical(identifier + ':' + line.strip())
class GenBank(object): """This class will handle GenBank files in various ways.""" def __init__(self, project, project_path=None, solo=False, multi=True, archive=False, min_fasta=True, blast=OrthoBlastN, **kwargs): """Handle GenBank files in various ways. It allows for refseq-release .gbff files to be downloaded from NCBI and uploaded to a BioSQL database (biopython). Single .gbk files can be downloaded from the .gbff, and uploaded to a custom BopSQL database for faster acquisition of GenBank data. :param project: The name of the project. :param project_path: The relative path to the project. :param solo: A flag for adding single fasta files. :param multi: A flag for adding multi-fasta files. :param archive: A flag for archiving current GenBank Data. # TODO :param min_fasta: A flag for minimizing FASTA file headers. :param blast: The blast parameter is used for composing various Orthologs.Blast classes. Can be a class, a dict, or none. :returns: .gbff files/databases, .gbk files/databases, & FASTA files. """ # TODO-ROB: Change the way the file systems work. self.project = project self.project_path = project_path self.solo = solo self.multi = multi self.min_fasta = min_fasta self.genbanklog = LogIt().default(logname="GenBank", logfile=None) # Configuration of class attributes add_self = attribute_config(self, composer=blast, checker=OrthoBlastN, checker2=BaseComparativeGenetics, project=project, project_path=project_path) for var, attr in add_self.__dict__.items(): setattr(self, var, attr) # Configuration # FIXME AttributeError: 'GenBank' object has no attribute 'user_db' self.target_gbk_db_path = self.user_db / Path(self.project) Path.mkdir(self.target_gbk_db_path, parents=True, exist_ok=True) # Make a list of BioSQL database(.db) files that contain GenBank info self.db_files_list = [] for FILE in os.listdir(str(self.ncbi_db_repo)): if FILE.endswith('.db'): self.db_files_list.append(str(FILE)) @staticmethod def name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode): """Name a fasta file. Provide a uniquely named FASTA file: * Coding sequence: * Single - "<path>/<gene>_<organism><feat_type_rank>.<extension>" * Multi - "<path>/<gene><feat_type_rank>.<extension>" * Other: * Single - "<path>/<gene>_<organism>_<feat_type_rank>.<extension>" * Multi - "<path>/<gene>_<feat_type_rank>.<extension>" :param path: The path where the file will be made. :param gene: The gene name. :param org: The organism name. :param feat_type: The type of feature from the GenBank record. (CDS, UTR, misc_feature, variation, etc.) :param feat_type_rank: The feature type + the rank. (There can be multiple misc_features and variations) :param extension: The file extension. (".ffn", ".faa", ".fna", ".fasta") :param mode: The mode ("w" or "a") for writing the file. Write to a solo-FASTA file. Append a multi-FASTA file. :return: The uniquely named FASTA file. """ # Create path variables. (typically raw_data/<gene>/GENBANK feat_path = path # Create a format-able string for file names if feat_type_rank is "CDS": single = '%s_%s%s%s' multi = '%s%s%s' else: single = '%s_%s_%s%s' multi = '%s_%s%s' # Create different names based on fasta file type if mode == 'w': file_path = feat_path / Path( single % (gene, org, feat_type_rank, extension)) elif mode == 'a': file_path = feat_path / Path(multi % (gene, feat_type_rank, extension)) # Make the base directory and return an open file. makedirectory(feat_path) file = open(file_path, mode) return file @staticmethod def protein_gi_fetch(feature): """Retrieve the protein gi number. :param feature: Search the protein feature for the GI number. :return: The protein GI number as a string. """ # Find the protein gi number under the features qualifiers. for x in feature.qualifiers: if 'GI' in x: _, _, p_gi = x.partition(':') return p_gi def create_post_blast_gbk_records(self, org_list, gene_dict): """Create a single GenBank file for each ortholog. After a blast has completed and the accession numbers have been compiled into an accession file, this class searches a local NCBI refseq release database composed of GenBank records. This method will create a single GenBank file (.gbk) for each ortholog with an accession number. The create_post_blast_gbk_records is only callable if the the instance is composed by one of the Blast classes. This method also requires an NCBI refseq release database to be set up with the proper GenBank Flat Files (.gbff) files. :param org_list: List of organisms :param gene_dict: A nested dictionary for accessing accession numbers. (e.g. gene_dict[GENE][ORGANISM} yields an accession number) :return: Does not return an object, but creates genbank files. """ # Parse the tier_frame_dict to get the tier for G_KEY, _ in self.tier_frame_dict.items(): tier = G_KEY # Parse the tier based transformed dataframe to get the gene for GENE in self.tier_frame_dict[tier].T: # Parse the organism list to get the desired accession number for ORGANISM in org_list: accession = str(gene_dict[GENE][ORGANISM]) accession, _, version = accession.partition('.') # When parsing a GenBank database, the version needs to be removed. accession = accession.upper() server_flag = False # Search the databases and create a GenBank file. self.get_gbk_file(accession, GENE, ORGANISM, server_flag=server_flag) def get_gbk_file(self, accession, gene, organism, server_flag=None): """Search a GenBank database for a target accession number. This function searches through the given NCBI databases (created by uploading NCBI refseq .gbff files to a BioPython BioSQL database) and creates single GenBank files. This function can be used after a blast or on its own. If used on it's own then the NCBI .db files must be manually moved to the proper directories. :param accession: Accession number of interest without the version. :param gene: Target gene of the accession number parameter. :param organism: Target organism of the accession number parameter. :param server_flag: (Default value = None) :return: """ gene_path = self.raw_data / Path(gene) / Path('GENBANK') Path.mkdir(gene_path, parents=True, exist_ok=True) # Parse each database to find the proper GenBank record for FILE in self.db_files_list: db_file_path = self.ncbi_db_repo / Path(FILE) # Stop searching if the GenBank record has been created. if server_flag is True: break server = BioSeqDatabase.open_database(driver='sqlite3', db=str(db_file_path)) # Parse the sub-databases for SUB_DB_NAME in server.keys(): db = server[SUB_DB_NAME] try: record = db.lookup(accession=accession) gbk_file = '%s_%s.gbk' % (gene, organism) gbk_file_path = gene_path / Path(gbk_file) with open(gbk_file_path, 'w') as GB_file: GB_file.write(record.format('genbank')) self.genbanklog.info(GB_file.name, 'created') # Make sure we have the correct GenBank file. self.gbk_quality_control(gbk_file_path, gene, organism) # Stop searching if the GenBank record has been created. server_flag = True break except IndexError: self.genbanklog.critical( 'Index Error in %s. Moving to the next database...' % SUB_DB_NAME) continue # If the file has not been created after searching, then raise an error if server_flag is not True: self.genbanklog.critical( "The GenBank file was not created for %s (%s, %s)." % (accession, gene, organism)) raise FileNotFoundError def gbk_quality_control(self, gbk_file, gene, organism): """Ensures the quality or validity of the retrieved genbank record. It takes the GenBank record and check to make sure the Gene and Organism from the GenBank record match the Gene and Organism from the accession file. If not, then the Blast has returned the wrong accession number. :param gbk_file: The path to a GenBank file. :param gene: A gene name from the Accession file. :param organism: A gene name from the Accession file. :return: """ # TODO-ROB: Check the bad data here against the misssing/duplicate files record = SeqIO.read(gbk_file, 'genbank') gene_flag = False organism_flag = False accession = record.id self.gbk_gene_synonym = {} self.duplicated_dict["validated"] = {} # Get the organism name from the GenBank file gbk_organism = record.features[0].qualifiers[ "organism"] # A list with one entry if len(gbk_organism) == 1: gbk_organism = gbk_organism[0] gbk_organism = gbk_organism.replace(" ", "_") else: self.genbanklog.critical( "Two organisms exist in the GenBank file. Is this normal?") raise BrokenPipeError # Check to make sure the organism in the GenBank file matches the # organism from the accession file if gbk_organism == organism: self.genbanklog.info( "The GenBank organism, %s, has been verified for %s." % (organism, gene)) else: organism_flag = True # Get the gene from the GenBank files gbk_genes = record.features[1].qualifiers["gene"] # Get the synonyms from the GenBank file if they exist and add them to # the list. if "gene_synonym" in str(record.features[1].qualifiers.keys()): base_gene_name = gbk_genes gbk_genes.extend(record.features[1].qualifiers["gene_synonym"]) # Create a dictionary from the synonyms self.gbk_gene_synonym[base_gene_name] = [] self.gbk_gene_synonym[base_gene_name].extend(gbk_genes) # Check to make sure the gene in the GenBank file matches the gene from # the accession file for gbk_gene in gbk_genes: if gbk_gene == gene: gene_flag = False self.genbanklog.info( "The GenBank gene, %s, has been verified for %s." % (gene, organism)) break else: gene_flag = True # TODO-ROB: Add a verified key to the duplicates dictionary. # Raise errors. if organism_flag is True and gene_flag is True: self.genbanklog.critical( "The organisms don't match.\n\tGenBank: %s \n\tAccession File: %s" % (gbk_organism, organism)) self.genbanklog.critical( "The genes don't match. \n\tGenBank: %s \n\tAccession File: %s" % (gbk_genes, gene)) raise BrokenPipeError elif organism_flag is True: self.genbanklog.critical( "The organisms don't match.\n\tGenBank: %s \n\tAccession File: %s" % (gbk_organism, organism)) raise BrokenPipeError elif gene_flag is True: self.genbanklog.critical( "The genes don't match. \n\tGenBank: %s \n\tAccession File: %s" % (gbk_genes, gene)) raise BrokenPipeError self.duplicated_dict["validated"][accession] = [gene, organism] def gbk_upload(self): """Upload a BioSQL database with target GenBank data (.gbk files). This method is only usable after creating GenBank records with this class. It uploads a BioSQL databases with target GenBank data (.gbk files). This creates a compact set of data for each project. :return: Does not return an object. """ t_count = 0 # Parse the tier dictionary for TIER in self.tier_frame_dict.keys(): db_name = str(TIER) + '.db' db_file_path = self.target_gbk_db_path / Path(db_name) # Create the db file if it exists if os.path.isfile(str(db_file_path)) is False: self.genbanklog.warn( 'Copying Template BioSQL Database... This may take a few minutes...' ) shutil.copy2('Template_BioSQL_DB.db', str(db_file_path)) # If it already exists then the database is bad, or needs to be update. Delete it. else: # TODO-ROB: This part is broken until the template db creation and management is added os.remove(str(db_file_path)) self.genbanklog.warn( 'Copying Template BioSQL Database... This may take a few minutes...' ) shutil.copy2('Template_BioSQL_DB.db', str(db_file_path)) server = BioSeqDatabase.open_database(driver='sqlite3', db=str(db_file_path)) gene_path = self.raw_data # Parse the raw_data folder to get the name of each gene. for GENE in os.listdir(str(gene_path)): sub_db_name = GENE genbank_path = gene_path / Path(GENE) / Path('GENBANK') # Parse the GenBank file names for each gene in order to upload them to a custom BioSQL database for FILE in os.listdir(str(genbank_path)): # Try to load the database. try: if sub_db_name not in server.keys(): server.new_database(sub_db_name) db = server[sub_db_name] count = db.load(SeqIO.parse(FILE, 'genbank')) server.commit() self.genbanklog.info('Server Commited %s' % sub_db_name) self.genbanklog.info('%s database loaded with %s.' % (db.dbid, FILE)) self.genbanklog.info( "That file contains %s genbank records." % str(count)) t_count = t_count + count self.genbanklog.info( 'The total number of files loaded so far is %i.' % t_count) # If the database cannot be loaded then rollback the server and raise an error. except BaseException: server.rollback() # Try to delete the sub database and commit try: del server[sub_db_name] server.commit() # If it cannot be deleted then raise an error. except BaseException: raise raise def get_fasta_files(self, acc_dict, db=True): """Create FASTA files for each GenBank record in the accession dictionary. It can search through a BioSQL database or it can crawl a directory for .gbk files. :param acc_dict: An accession dictionary like the one created by CompGenObjects. :param db: A flag that determines whether or not to use the custom BioSQL database or to use .gbk files. (Default value = True) :return: Returns FASTA files for each GenBank record. """ # Get FASTA files from the BioSQL GenBank databases. if db is True: # Parse the directory that contains the databases for the project of interest. for database in os.listdir(str(self.target_gbk_db_path)): server = BioSeqDatabase.open_database(driver="sqlite3", db=database) try: for db_name in server.keys(): db = server[db_name] # For each GenBank record in the database write a set of FASTA files. for item in db.keys(): record = db.lookup(item) self.write_fasta_files(record, acc_dict) self.genbanklog.info( "FASTA files for %s created from BioSQL database." % item) except: raise () # Get FASTA files from the GenBank files. # TODO-ROB change this. Broken by new directory structure # TODO-ROB directory looks like /raw_data/Gene_1/GENBANK/*.gbk elif db is False: # Parse the directory that contain the GenBank records for the project of interest. for _, _, gbk_files in os.walk(str(self.target_gbk_files_path)): # For each genbank record write a set of FASTA files. for gbk_file in gbk_files: if Path(gbk_file).suffix is '.gbk': record = SeqIO.read(gbk_file, 'genbank') self.write_fasta_files(record, acc_dict) self.genbanklog.info("FASTA files for %s created." % gbk_file) def write_fasta_files(self, record, acc_dict): """Create a dictionary for formatting the FASTA header & sequence. :param record: A GenBank record created by BioPython. :param acc_dict: Accession dictionary from the CompGenObjects class. :return: """ feat_type_list = [] for feature in record.features: # XXX Set up variables to use for dictionary values !!! # Basic variables. accession = record.id gene = acc_dict[accession][0] organism = acc_dict[accession][1] # Variable for minimalistic FASTA files. genus, sep, species = organism.partition('_') min_org = str(''.join([genus[0], sep, species[0:28]])) # Keep a list of feature types to identify duplicates (for naming the FASTA files). # The first iteration of the feature type contains no number. # The following iterations are concatenated with a number. feat_type = str(feature.type) feat_type_list.append(feat_type) duplicate_num = feat_type_list.count(feat_type) if duplicate_num == 1: feat_type_rank = feat_type else: feat_type_rank = feat_type + str(duplicate_num) # XXX END !!! # TODO-ROB: Remove the GI number stuff here or at least prepare for # file with no GI. # Create a dictionary and format FASTA file entries. fmt = { 'na_gi': str(record.annotations['gi']), 'aa_gi': str(self.protein_gi_fetch(feature)), 'na_acc_n': str(accession), 'aa_acc_n': str(feature.qualifiers['protein_id'][0]), 'na_description': str(record.description), 'aa_description': str(feature.qualifiers['product'][0]), 'na_seq': str(feature.extract(record.seq)), 'aa_seq': str(feature.qualifiers['translation'][0]), 'na_misc_feat': str(feature.qualifiers['note'][0]), 'org': str(organism), 'gene': str(gene), 'min_org': str(min_org), 'feat_type': str(feat_type), 'feat_type_rank': str(feat_type_rank), 'path': str(self.raw_data / Path(gene) / Path('GENBANK')) } # Set up minimalistic FASTA headers and sequence entries for Nucleic Acid and Amino Acid sequences. na_entry = ">{min_org}\n{na_seq}\n".format(**fmt) aa_entry = ">{min_org}\n{aa_seq}\n".format(**fmt) # For full FASTA headers/sequences set min_fasta to False if self.min_fasta is False: na_entry = ">gi|{na_gi}|ref|{na_acc_n}| {na_description}\n{na_seq}\n".format( **fmt) aa_entry = ">gi|{aa_gi}|reg|{aa_acc_n}| {aa_description} {org}\n{aa_seq}\n".format( **fmt) # ######### End ######### # # ############ Write desired FASTA files ############ # if self.solo is True: self.solo_fasta(na_entry, aa_entry, fmt) if self.multi is True: self.multi_fasta(na_entry, aa_entry, fmt) def solo_fasta(self, na_entry, aa_entry, fmt): """This method writes a sequence of a feature to a uniquely named file using a dictionary for formatting. :param na_entry: A string representing the Nucleic Acid sequence data in FASTA format. :param aa_entry: A string representing the Amino Acid sequence data in FASTA format. :param fmt: A dictionary for formatting the FASTA entries and the file names. :return: Does not return an object, but creates single entry FASTA files. """ mode = 'w' # Create the desired variables from the formatter dictionary. feat_type = fmt['feat_type'] feat_type_rank = fmt['feat_type_rank'] path = fmt['path'] gene = fmt['gene'] org = fmt['org'] if feat_type == "CDS": # Create a .ffn file (FASTA for Coding Nucleic Acids) extension = '.ffn' file = self.name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode) file.write(na_entry) file.close() # Create a .faa file (FASTA for Amino Acids) extension = '.faa' file = self.name_fasta_file(path, gene, org, 'Protein', feat_type_rank, extension, mode) file.write(aa_entry) file.close() elif feat_type == "misc_feature": # Create a custom entry for miscellaneous features. na_entry = ">gi|{na_gi}|ref|{na_acc_n}| {na_description} Feature: {na_misc_feat}\n{na_seq}\n".format( **fmt) # Creates .fna files (generic FASTA file for Nucleic Acids) extension = '.fna' file = self.name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode) file.write(na_entry) file.close() elif feat_type != "variation": # Creates .fasta files (generic FASTA file) extension = '.fasta' file = self.name_fasta_file(path, gene, org, 'Other', feat_type_rank, extension, mode) file.write(na_entry) file.close() def multi_fasta(self, na_entry, aa_entry, fmt): """Append an othologous sequence of a feature to a uniquely named file. Usese a dictionary for formatting. :param na_entry: A string representing the Nucleic Acid sequence data in FASTA format. :param aa_entry: A string representing the Amino Acid sequence data in FASTA format. :param fmt: A dictionary for formatting the FASTA entries and the file names. :return: Does not return an object, but creates or appends to a multi entry FASTA file. """ mode = 'a' # Create the desired variables from the formatter dictionary. feat_type = fmt['feat_type'] feat_type_rank = fmt['feat_type_rank'] path = fmt['path'] gene = fmt['gene'] org = fmt['org'] if feat_type == "CDS": # Create a MASTER .ffn file (multi-FASTA file for Coding Nucleic Acids) extension = '.ffn' file = self.name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode) file.write(na_entry) file.close() # Create a MASTER .faa file (multi-FASTA file for Amino Acids) extension = '.faa' file = self.name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode) file.write(aa_entry) file.close() elif feat_type == "misc_feature": na_entry = ">gi|{na_gi}|ref|{na_acc_n}| {na_description} Feature: {na_misc_feat}\n{na_seq}\n".format( **fmt) # Creates .fna files (generic FASTA file for Nucleic Acids) extension = '.fna' file = self.name_fasta_file(path, gene, org, feat_type, feat_type_rank, extension, mode) file.write(na_entry) file.close()
class Oven(object): """Class that deploys cookiecutter templates.""" def __init__(self, repo=None, user=None, project=None, basic_project=False, website=None, db_repo="databases", output_dir=os.getcwd(), recipes=CookBook()): """Deploy custom cookiecutter templates: The Oven uses the different Ingredients (parameters/attributes) and the Cook Book(cookiecutter templates) to bake_the_cookies in the Oven(class methods). After the cookies cool, they are put in the cookie_jar (output directory). :param repo (string): An ingredient representing the repository name. :param user (string): An ingredient representing the user name :param project (string): An ingredient representing the project name. :param basic_project (bool): A secret ingredient ONLY for the basic project cookie. :param db_config_file (list): An ingredient representing a list of db_config_file. :param website (string): An ingredient representing the website name. :param output_dir (path or pathlike): The cookie jar for storing the cookies. :param recipes (pathlike): An index for the different recipe templates. """ self.cookielog = LogIt().default(logname="Cookies", logfile=None) self.cookie_jar = output_dir # Below are the PyPi path strings # The first group is to access the cookiecutter templates self.repo = repo self.user = user self.project = project self.basic_project = basic_project self.website = website self.db_repo = db_repo self.Recipes = recipes self.Ingredients = {"repo": self.repo, "user": self.user, "project": self.project, "basic_project": self.basic_project, "website": self.website, "db_repo": self.db_repo, "recipes": self.Recipes.__dict__} def bake_the_repo(self, cookie_jar=None): """Create a new repository. This function creates a new repository. If a repository name is given to the class then it is given a name. If not, cookiecutters takes input from the user. The base class will be the only class that allows cookiecutters parameter no_input to be False. :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the Repository Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar if self.repo: no_input = True e_c = { "repository_name": self.repo } else: no_input = False e_c = None # TODO-ROB change cookiecutter so that it can take pathlike objects cookiecutter(str(self.Recipes.repo_cookie), no_input=no_input, extra_context=e_c, output_dir=str(self.cookie_jar)) os.chmod(str(self.cookie_jar / Path(self.repo)), mode=0o777) self.cookielog.info('Repository directories have been created. ✔') def bake_the_user(self, cookie_jar=None): """Create a new directory system for the active user. This function uses the username given by our FLASK framework and creates a new directory system for the active user using our new_user cookiecutter template. :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the User Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar # This is used ONLY when the user registers in flask # TODO-ROB: Create the cookiecutter.json file # extra_context overrides user and default configs cookiecutter(str(self.Recipes.user_cookie), no_input=True, extra_context={ "user_name": self.user}, output_dir=str(self.cookie_jar)) # Change user permissions with flask later (this is for testing # purposes os.chmod(str(self.cookie_jar / Path(self.user)), mode=0o777) self.cookielog.info('Directories have been created for the user, %s. ✔' % self.user) def bake_the_project(self, cookie_jar=None): """Create a new project in the user's directory. :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the Project Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar # Add the project if self.project: no_input = True e_c = {"project_name": self.project} project_log_message = "(%s)" % self.project else: no_input = False e_c = None project_log_message = "that has been named with user input" if not self.basic_project: self.cookielog.warn('A project linked to a user/repository is being created.') cookiecutter(str(self.Recipes.project_cookie), extra_context=e_c, no_input=no_input, output_dir=str(self.cookie_jar)) # Logging if self.user: self.cookielog.info('Directories have been created for %s\'s project %s. ✔' % (self.user, project_log_message)) else: self.cookielog.info('Directories have been created for %s.' % project_log_message) else: self.cookielog.warn('A basic standalone project is being created.') cookiecutter(str(self.Recipes.basic_project_cookie), extra_context=e_c, no_input=no_input, output_dir=str(self.cookie_jar)) self.cookielog.info('Directories have been created for a standalone project %s. ✔' % project_log_message) os.chmod(str(self.cookie_jar / Path(self.project)), mode=0o777) def bake_the_db_repo(self, db_config_file, db_path, cookie_jar=None, archive_flag=False, delete=False): """Create a database directory. :param db_config_file: :param db_path: :param cookie_jar: (Default value = None) :param archive_flag: (Default value = False) :param delete: (Default value = False) :return: A new database inside the users database directory """ # TODO-ROB: Work work this in with the database management class. if cookie_jar: self.cookie_jar = cookie_jar # TODO-ROB: Rework this for new archive function. # if archive_flag: # archive_list = archive(database_path=db_path, archive_path=self.cookie_jar, config_file=db_config_file, delete_flag=delete) # for arch in archive_list: # self.cookielog.info("An archive has been created at %s." % arch) # else: # if self.db_repo: # no_input = True # e_c = {"db_name": self.db_repo} # else: # no_input = False # e_c = None cookiecutter(str(self.Recipes.db_cookie), extra_context=e_c, no_input=no_input, output_dir=str(self.cookie_jar)) self.cookielog.info("Directories have been created for a database repository %s." % str((self.cookie_jar / Path(self.db_repo)))) os.chmod(str(self.cookie_jar / Path(self.db_repo)), mode=0o777) # # for db_key, db_value in db_config_dict["Database_Config"].items(): # if db_value: # pass # TODO-ROB: Use db_value system with database management configuration. def bake_the_website(self, host, port, website_path, cookie_jar=None): """Create a website using the new_website cookie. After creating the directory structure, the run_script function from cookiecutter finds the hooks folder which contains a post-cookiecutter-template-generation bash script. The bash script sets up the proper dependencies and environment variables for the website, and runs the website on the specified host and port. :param host: :param port: :param website_path: :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the Website Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar # TODO-ROB: Add heavy logging here e_c = {"website_name": self.website, "website_path": os.path.join(str(website_path), ''), "website_host": host, "website_port": port} cookiecutter(str(self.Recipes.website_cookie), no_input=True, extra_context=e_c, output_dir=str(self.cookie_jar)) os.chmod(str(self.cookie_jar / Path(self.website)), mode=0o777) # Get the absolute path to the script that starts the flask server script_path = website_path / \ Path('hooks') / Path('post_gen_project.sh') #scripts_file_path = find_hook('post_gen_project.sh', hooks_dir=str(script_path)) # TODO-ROB add screening to the bash script for flask run -h -p run_script(script_path=str(script_path), cwd=str(website_path)) self.cookielog.info('Directories have been created for the Flask Web Server, %s. ✔' % self.website) self.cookielog.warn('The %s Flask Server should now be running on http://%s:%s' % (self.website, host, port)) def bake_the_research(self, research_type, research, cookie_jar=None): """Create a directory for a new research project. :param research_type: :param research: :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the Research Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar e_c = {"research_type": research_type, "research_name": research} cookiecutter(str(self.Recipes.research_cookie), no_input=True, extra_context=e_c, output_dir=str(self.cookie_jar)) os.chmod(str(self.cookie_jar / Path(research_type)), mode=0o777) # script_path = self.project_cookie / Path('hooks') / Path('post_gen_project.py') # run_script(script_path, ) self.cookielog.info('Directories have been created for the %s research project, %s. ✔' % (research_type, research)) def bake_the_app(self, app, cookie_jar=None): """Create an app. :param app: Name of the app. :param cookie_jar: (Default value = None) """ self.cookielog.warn('Creating directories from the App Cookie template.') if cookie_jar: self.cookie_jar = cookie_jar e_c = {"app_name": app} cookiecutter(str(self.Recipes.app_cookie), no_input=True, extra_context=e_c, output_dir=str(self.cookie_jar)) os.chmod(str(self.cookie_jar), mode=0o777) self.cookielog.info("Directories have been created for an R-Shiny app, %s. ✔" % app)