Beispiel #1
0
 def __init__(self, host, user=None, pswd=None, cert=None):
     """
     Initializes a database of style curator.
     
     Parameters
     ----------
     host : str
         The host name (url) for the database.
     user : str or tuple of two str
         The username to use for accessing the database.  Alternatively, a
         tuple of (user, pswd).
     pswd : str, optional
         The password associated with user to use for accessing the database.
         This can either be the password as a str, or a str path to a file
         containing only the password. If not given, a prompt will ask for the
         password.
     cert : str, optional
         The path to a certification file if needed for accessing the database.
     """
     
     # Pass parameters to cdcs object
     if Path(pswd).is_file():
         with open(pswd) as f:
             pswd = f.read().strip()
     self.cdcs = CDCS(host, username=user, password=pswd, certification=cert)
     
     # Pass host to Database initializer
     Database.__init__(self, host)
Beispiel #2
0
 def cdcs(self):
     """A cdcs object with anonymous user. Create only once"""
     try:
         return self.__cdcs
     except:
         self.__cdcs = CDCS(host=self.host, username='')
         return self.__cdcs
Beispiel #3
0
    def test_init_testcall(self):
        """This function tests CDCS init and the associated testcall"""
        
        # Test #1: anonymous cdcs does no testcall
        assert self.cdcs.host == self.host
        assert self.cdcs.username is None
        assert self.cdcs.cert is None
        assert self.cdcs.verify is True
        
        # Test #2: Mock response for valid username + password
        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, f'{self.host}/rest/data/', status=200, json=[])
            cdcs = CDCS(host=self.host, username='******', password='******')
            assert cdcs.username == 'Me'

        # Test #3: Mock response for invalid username + password
        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, f'{self.host}/rest/data/', status=401,
                     json={'detail': 'Invalid username/password.'})
            with raises(requests.HTTPError):
                cdcs = CDCS(host=self.host, username='******', password='******')
        
        # Test #4: Call testcall with bad url
        with raises(requests.ConnectionError):
            self.cdcs.testcall()
            
        # Test #5: Mock testcall with good url but no permissions
        with responses.RequestsMock() as rsps:
            rsps.add(responses.GET, f'{self.host}/rest/data/', status=401,
                     json={'detail': 'Authentication credentials were not provided.'})           
            with raises(requests.HTTPError):
                cdcs.testcall()
Beispiel #4
0
class CDCSDatabase(Database):
    
    def __init__(self, host, user=None, pswd=None, cert=None):
        """
        Initializes a database of style curator.
        
        Parameters
        ----------
        host : str
            The host name (url) for the database.
        user : str or tuple of two str
            The username to use for accessing the database.  Alternatively, a
            tuple of (user, pswd).
        pswd : str, optional
            The password associated with user to use for accessing the database.
            This can either be the password as a str, or a str path to a file
            containing only the password. If not given, a prompt will ask for the
            password.
        cert : str, optional
            The path to a certification file if needed for accessing the database.
        """
        
        # Pass parameters to cdcs object
        if Path(pswd).is_file():
            with open(pswd) as f:
                pswd = f.read().strip()
        self.cdcs = CDCS(host, username=user, password=pswd, certification=cert)
        
        # Pass host to Database initializer
        Database.__init__(self, host)
    
    def get_records(self, name=None, style=None, query=None, return_df=False,
                    **kwargs):
        """
        Produces a list of all matching records in the database.
        
        Parameters
        ----------
        name : str, optional
            The record name or id to limit the search by.
        style : str, optional
            The record style to limit the search by.
            
        Returns
        ------
        list of iprPy.Records
            All records from the database matching the given parameters.
        """
        
        records = []

        def build_records(series):
            return load_record(style=series.template_title, name=series.title, content=series.xml_content)

        for n in iaslist(name):
            data = self.cdcs.query(title=n, template=style, mongoquery=query)
            records.extend(data.apply(build_records, axis=1))

        if return_df or len(kwargs) > 0:
            df = []
            for record in records:
                df.append(record.todict(full=False, flat=True))
            df = pd.DataFrame(df)
        
            if len(df) > 0:
                for key in kwargs:
                    df = df[df[key].isin(aslist(kwargs[key]))]
            
            if return_df:
                return list(records[df.index.tolist()]), df.reset_index(drop=True)
            else:
                return list(records[df.index.tolist()])
        else:
            return records
    
    def get_records_df(self, name=None, style=None, query=None, full=True,
                       flat=False, **kwargs):
        """
        Produces a list of all matching records in the database.
        
        Parameters
        ----------
        name : str, optional
            The record name or id to limit the search by.
        style : str, optional
            The record style to limit the search by.
            
        Returns
        ------
        list of iprPy.Records
            All records from the database matching the given parameters.
        """
        
        df = []
        def build_record_dicts(series, full, flat):
            return load_record(style=series.template_title, name=series.title,
                                     content=series.xml_content).todict(full=full, flat=flat)
        
        for n in iaslist(name):
            data = self.cdcs.query(title=n, template=style, mongoquery=query)
            df.extend(data.apply(build_record_dicts, args=[full, flat], axis=1))
        df = pd.DataFrame(df)
        
        if len(df) > 0:
            for key in kwargs:
                df = df[df[key].isin(aslist(kwargs[key]))]
        
        return df.reset_index(drop=True)
    
    def get_record(self, name=None, style=None, query=None, **kwargs):
        """
        Returns a single matching record from the database.
        
        Parameters
        ----------
        name : str, optional
            The record name or id to limit the search by.
        style : str, optional
            The record style to limit the search by.
            
        Returns
        ------
        iprPy.Record
            The single record from the database matching the given parameters.
        
        Raises
        ------
        ValueError
            If multiple or no matching records found.
        """
        
        # Get records
        record = self.get_records(name=name, style=style, query=query, **kwargs)
        
        # Verify that there is only one matching record
        if len(record) == 1:
            return record[0]
        elif len(record) == 0:
            raise ValueError(f'Cannot find matching record {name} ({style})')
        else:
            raise ValueError('Multiple matching records found')
    
    def add_record(self, record=None, style=None, name=None, content=None):
        """
        Adds a new record to the database.  Will issue an error if a 
        matching record already exists in the database.
        
        Parameters
        ----------
        record : iprPy.Record, optional
            The new record to add to the database.  If not given, then name,
            style and content are required.
        name : str, optional
            The name to assign to the new record.  Required if record is not
            given.
        style : str, optional
            The record style for the new record.  Required if record is not
            given.
        content : str, optional
            The xml content of the new record.  Required if record is not
            given.
            
        Returns
        ------
        iprPy.Record
            Either the given record or a record composed of the name, style, 
            and content.
        
        Raises
        ------
        ValueError
            If style, name and/or content given with record, or a matching record
            already exists.
        """
        
        # Create Record object if not given
        if record is None:
            record = load_record(style, name, content)
        
        # Issue a ValueError for competing kwargs
        elif style is not None or name is not None or content is not None:
            raise ValueError('kwargs style, name, and content cannot be given with kwarg record')
            
        # Upload record to database
        self.cdcs.upload_record(template=record.style, content=record.content.xml(), title=record.name)
        
        return record
    
    def update_record(self, record=None, style=None, name=None, content=None):
        """
        Replaces an existing record with a new record of matching name and 
        style, but new content.  Will issue an error if exactly one 
        matching record is not found in the databse.
        
        Parameters
        ----------
        database_info : mdcs.MDCS
            The MDCS class used for accessing the curator database.
        record : iprPy.Record, optional
            The record with new content to update in the database.  If not 
            given, content is required along with name and/or style to 
            uniquely define a record to update.
        name : str, optional
            The name to uniquely identify the record to update.
        style : str, optional
            The style of the record to update.
        content : str, optional
            The new xml content to use for the record.  Required if record is
            not given.
            
        Returns
        ------
        iprPy.Record
            Either the given record or a record composed of the name, style, 
            and content.
            
        Raises
        ------
        TypeError
            If no new content is given.
        ValueError
            If style and/or name content given with record.
        """
        
        # Create Record object if not given
        if record is None:
            if content is None:
                raise TypeError('no new content given')
            oldrecord = self.get_record(name=name, style=style)
            record = load_record(oldrecord.style, oldrecord.name, content)
        
        # Use given record object
        else:
            if style is not None or name is not None:
                raise ValueError('kwargs style and name cannot be given with kwarg record')
            
            # Replace content in record object
            if content is not None:
                record = load_record(record.style, record.name, content)
        
        # Upload record to database
        self.cdcs.update_record(template=record.style, content=record.content.xml(), title=record.name)
        
        return record
    
    def delete_record(self, record=None, name=None, style=None):
        """
        Permanently deletes a record from the database.  Will issue an error 
        if exactly one matching record is not found in the database.
        
        Parameters
        ----------
        record : iprPy.Record, optional
            The record to delete from the database.  If not given, name and/or
            style are needed to uniquely define the record to delete.
        name : str, optional
            The name of the record to delete.
        style : str, optional
            The style of the record to delete.
        
        Raises
        ------
        ValueError
            If style and/or name content given with record.
        """
        
        # Extract values from Record object if given
        if record is not None:
            if style is not None or name is not None:
                raise ValueError('kwargs style and name cannot be given with kwarg record')
            name = record.name
            style = record.style
         
        # Delete record
        self.cdcs.delete_record(template=style, title=name)
    
    def add_tar(self, record=None, name=None, style=None, tar=None, root_dir=None):
        """
        Archives and stores a folder associated with a record.  Issues an
        error if exactly one matching record is not found in the database, or
        the associated record already has a tar archive.
        
        Parameters
        ----------
        database_info : mdcs.MDCS
            The MDCS class used for accessing the curator database.
        record : iprPy.Record, optional
            The record to associate the tar archive with.  If not given, then
            name and/or style necessary to uniquely identify the record are
            needed.
        name : str, optional
            .The name to use in uniquely identifying the record.
        style : str, optional
            .The style to use in uniquely identifying the record.
        tar : bytes, optional
            The bytes content of a tar file to save.  tar cannot be given
            with root_dir.
        root_dir : str, optional
            Specifies the root directory for finding the directory to archive.
            The directory to archive is at <root_dir>/<name>.  (Default is to
            set root_dir to the current working directory.)  tar cannot be given
            with root_dir.
        
        Raises
        ------
        ValueError
            If style and/or name content given with record or the record already
            has an archive.
        """
        
        # Get Record object if not given
        if record is None:
            record = self.get_record(name=name, style=style)
        
        else:
            # Issue a ValueError for competing kwargs
            if style is not None or name is not None:
                raise ValueError('kwargs style and name cannot be given with kwarg record')

            # Verify that record exists
            record = self.get_record(name=record.name, style=record.style)
        
        # Check if an archive already exists
        blobs = self.cdcs.get_blobs(filename=record.name)
        if len(blobs) > 0:
            raise ValueError('Record already has an archive')
        
        filename = Path(record.name + '.tar.gz')

        # Create directory archive and upload
        if tar is None: 
            
            # Make archive
            shutil.make_archive(record.name, 'gztar', root_dir=root_dir,
                                base_dir=record.name)
            
            # Upload archive
            tries = 0
            while tries < 2:
                if True:
                    url = self.cdcs.upload_blob(filename.as_posix())
                    break
                else:
                    tries += 1
            if tries == 2:
                raise ValueError('Failed to upload archive 2 times')
            
            # Remove local archive copy
            filename.unlink()
        
        # Upload pre-existing tar object
        elif root_dir is None:
            # Upload archive
            tries = 0
            while tries < 2:
                if True:
                    url = self.cdcs.upload_blob(filename=filename, blobbytes=BytesIO(tar))
                    break
                else:
                    tries += 1
            if tries == 2:
                raise ValueError('Failed to upload archive 2 times')
        
        else:
            raise ValueError('tar and root_dir cannot both be given')

    def get_tar(self, record=None, name=None, style=None, raw=False):
        """
        Retrives the tar archive associated with a record in the database.
        Issues an error if exactly one matching record is not found in the 
        database.
        
        Parameters
        ----------
        database_info : mdcs.MDCS
            The MDCS class used for accessing the curator database.
        record : iprPy.Record, optional
            The record to retrive the associated tar archive for.
        name : str, optional
            .The name to use in uniquely identifying the record.
        style : str, optional
            .The style to use in uniquely identifying the record.
        raw : bool, optional
            If True, return the archive as raw binary content. If 
            False, return as an open tarfile. (Default is False)
            
        Returns
        -------
        tarfile or str
            The tar archive as an open tarfile if raw=False, or as a binary str if
            raw=True.
            
        Raises
        ------
        ValueError
            If style and/or name content given with record.
        """

        # Create Record object if not given
        if record is None:
            record = self.get_record(name=name, style=style)
        
        # Issue a TypeError for competing kwargs
        elif style is not None or name is not None:
            raise TypeError('kwargs style and name cannot be given with kwarg record')
        
        # Verify that record exists
        else:
            record = self.get_record(name=record.name, style=record.style)
        
        filename = Path(record.name + '.tar.gz')

        # Download tar file
        tardata = self.cdcs.get_blob_contents(filename=filename)
        
        # Return contents
        if raw is True:
            return tardata
        else:
            return tarfile.open(fileobj = BytesIO(tardata))
    
    def delete_tar(self, record=None, name=None, style=None):
        # Create Record object if not given
        if record is None:
            record = self.get_record(name=name, style=style)
        
        # Issue a TypeError for competing kwargs
        elif style is not None or name is not None:
            raise TypeError('kwargs style and name cannot be given with kwarg record')
        
        # Verify that record exists
        else:
            record = self.get_record(name=record.name, style=record.style)
        
        filename = Path(record.name + '.tar.gz')

        self.cdcs.delete_blob(filename=filename)

    def update_tar(self, record=None, name=None, style=None, tar=None, root_dir=None):
        """
        Archives and stores a folder associated with a record.  Issues an
        error if exactly one matching record is not found in the database, or
        the associated record already has a tar archive.
        
        Parameters
        ----------
        database_info : mdcs.MDCS
            The MDCS class used for accessing the curator database.
        record : iprPy.Record, optional
            The record to associate the tar archive with.  If not given, then
            name and/or style necessary to uniquely identify the record are
            needed.
        name : str, optional
            .The name to use in uniquely identifying the record.
        style : str, optional
            .The style to use in uniquely identifying the record.
        tar : bytes, optional
            The bytes content of a tar file to save.  tar cannot be given
            with root_dir.
        root_dir : str, optional
            Specifies the root directory for finding the directory to archive.
            The directory to archive is at <root_dir>/<name>.  (Default is to
            set root_dir to the current working directory.)  tar cannot be given
            with root_dir.
        
        Raises
        ------
        ValueError
            If style and/or name content given with record or the record already
            has an archive.
        """
        
        self.delete_tar(record=record, name=name, style=style)
        self.add_tar(record=record, name=name, style=style, tar=tar, root_dir=root_dir)
Beispiel #5
0
    def __init__(self, host=None, username=None, password=None, certification=None,
                 localpath=None, verbose=False, local=True, remote=True, 
                 load=False, status='active'):
        """
        Class initializer

        Parameters
        ----------
        host : str, optional
            CDCS site to access.  Default value is 'https://potentials.nist.gov/'.
        username : str, optional 
            User name to use to access the host site.  Default value of '' will
            access the site as an anonymous visitor.
        password : str, optional
            Password associated with the given username.  Not needed for
            anonymous access.
        certification : str, optional
            File path to certification file if needed for host.
        localpath : str, optional
            Path to the local library directory to use.  If not given, will use
            the set library_directory setting.
        verbose : bool, optional
            If True, info messages will be printed during operations.  Default
            value is False.
        local : bool, optional
            Indicates if the load operations will check localpath for records.
            Default value is True.
        remote : bool, optional
            Indicates if the load operations will download records from the
            remote database.  Default value is True.  If a local copy exists,
            then setting this to False is considerably faster.
        load : bool, str or list, optional
            If True, citations, potentials and lammps_potentials will all be
            loaded during initialization. If False (default), none will be
            loaded.  Alternatively, a str or list can be given to specify which
            of the three record types to load.
        status : str, list or None, optional
            Only potential_LAMMPS records with the given status(es) will be
            loaded.  Allowed values are 'active' (default), 'superseded', and
            'retracted'.  If None is given, then all potentials will be loaded.
        """
        # Set default database parameters
        if host is None:
            host = 'https://potentials.nist.gov/'
        if username is None:
            username = ''
        
        # Create the underlying CDCS client
        self.__cdcs = CDCS(host=host, username=username, password=password,
                           certification=certification)
        
        # Define class attributes
        if localpath is None:
            self.__localpath = Settings().library_directory
        else:
            self.__localpath = Path(localpath)
        assert isinstance(local, bool)
        assert isinstance(remote, bool)
        self.__local = local
        self.__remote = remote

        # Load records
        if load is True:
            self.load_all(verbose=verbose)
        elif load is False:
            self._no_load_citations()
            self._no_load_potentials()
            self._no_load_lammps_potentials()
        else:
            load = aslist(load)
        
            if 'citations' in load:
                self.load_citations(verbose=verbose)
                load.remove('citations')
            else:
                self._no_load_citations()
            if 'potentials' in load:
                self.load_potentials(verbose=verbose)
                load.remove('potentials')
            else:
                self._no_load_potentials()
            if 'lammps_potentials' in load:
                self.load_lammps_potentials(verbose=verbose, status=status)
                load.remove('lammps_potentials')
            else:
                self._no_load_lammps_potentials()

            if len(load) > 0:
                raise ValueError('unknown load type: allowed values are citations, potentials, and lammps_potentials')