Source code for filerecords.api.file_record

"""
`FileRecord` is the basic object in which records within a registry are accessed.
This object can access and edit records for a specific file within the registry.

API Usage
=========

In the code API, a `Registry` will return a `FileRecord` object for a specific file when a record is accessed.
Each `FileRecord` requires a `Registry` object as parent to be initialized, and can then either access the registry
records via the internal id (happens when records are accessed via the `Registry` object) or via the file path which is then
searched for in the registry. It is suggested to access the records directly via the `Registry` object only.


Once a `FileRecord` has been obtained from the registry its comments and flags can be accessed using the `comments` and `flags` attributes.

.. code-block:: python

    from filerecords.api import Registry

    reg = Registry()

    # Access a record via the registry
    record = reg.get_record( "path/to/file" )

    # Access the comments
    comments = record.comments

    # Access the flags
    flags = record.flags
    

The `FileRecord` object can also be used to add new comments and flags to the record.

.. code-block:: python

    record.add_comment( "the new comment" )
    record.add_flag( ["flag1", "flag2"] )


When adding comments or flags it is important to save the record afterward. 
This will automatically also save the parent registry.

.. code-block:: python

    # Save the record (will also save the registry)
    record.save()


"""

from datetime import datetime
import uuid
import os

import filerecords.api.base as base
# import filerecords.api.registry as registry
import filerecords.api.utils as utils
import filerecords.api.settings as settings

logger = utils.log()

[docs]class FileRecord(base.BaseRecord): """ This class represents a single file record entry. Parameters ---------- registry : Registry The registry the file record is associated with. id : str The unique identifier of the file record. If none is provided, a new id is created. filename : str The filename of the file to record (if a new record is being created). """ def __init__( self, registry, id : str = None, filename : str = None ): super().__init__() self.registry = registry self.filename = os.path.join( os.path.join( self.registry.directory, filename ) ) if filename else None if id is None: if filename is None: raise ValueError( "A filename must be provided when adding a new file record." ) _init_new = True self.id = uuid.uuid4() else: _init_new = False self.id = id self.relpath = self._get_relpath() self.filename = self._get_filename() self.metafile = os.path.join( self.registry.registry_dir, str(self.id) ) if _init_new: utils._init_entryfile( self.registry.registry_dir, str(self.id) ) self.load() logger.debug( f"filename: {self.filename}" )
[docs] def load( self ): """ Loads the file records. Note ---- This is done automatically during init if an existing file is specified. """ super().load( self.metafile )
[docs] def save( self ): """ Save the file record. This will also save the the registry state at the same time. """ super().save() self.registry.save()
[docs] def add_flags( self, flags : str or list ): """ Add flags to the metadata. Note ---- This will not automatically save the metadata, use the save() method to do so. Parameters ---------- flags : str or list The flag(s) to add. This can also be a defined flag-group label. """ get_group_flags = lambda x: self.registry.groups[x] if x in self.registry.groups else [x] if isinstance( flags, list ): _flags = [] for flag in flags: _flags += get_group_flags( flag ) flags = _flags del _flags else: flags = get_group_flags( flags ) super().add_flags( flags ) self.registry.add_flags( flags )
[docs] def to_markdown( self, comments_header : bool = True ): """ Convert the metadata to a markdown representation. Parameters ---------- comments_header : bool Add a header above the comments. """ # the [3:] is to remove the ../ in the relpath beginning # since every relpath starts at the base directory which is one # level above the registry directory. path = self.relpath[3:].replace( "_", "\_" ) text = f"### {path}\n\n" if len( self.flags ) == 0: flags = "No flags" else: flags = '\n- '.join( self.flags ) text += f"- {flags}\n\n" if comments_header: text += "#### Comments\n\n" if len( self.comments ) == 0: text += "No comments\n\n" else: for timestamp in self.comments: comment, user = list( self.comments[timestamp].values() ) text += f"{settings.comment_format( comment, user, timestamp)}\n\n" return text
[docs] def to_yaml( self, timestamp : bool = False, filename : str = None ): """ Convert the source registry to a single YAML file. Parameters ---------- timestamp : bool Add a timestamp in the markdown. filename : str (optional) The filename of the yaml file to create. If none is provided, no file is created. Returns ------- dict The assembled dictionary of the registry. """ _dict = dict( self.metadata ) _dict["filename"] = self.filename _dict["relpath"] = self.relpath[3:] # the [3:] is to remove the ../ in front of every relpath if timestamp: _dict["timestamp"] = datetime.now().strftime( "%Y-%m-%d %H:%M:%S" ) if filename is not None: utils.save_yamlfile( filename, _dict ) return _dict
def _get_relpath(self): """ Make the path of the recorded file relative to the registry. """ # logger.debug( f"Registry index: {self.registry.index}" ) ids = self.registry.index.id.astype(str) # logger.debug( f"ids={ids}" ) logger.debug( f"str(self.id)={str(self.id)}" ) if str(self.id) in ids: return self.registry.index.loc[ids == str(self.id), "relpath"].values[0] else: relpath = os.path.relpath( self.filename, self.registry.registry_dir ) if relpath in self.registry.index.relpath.values: directory = os.path.relpath( os.path.dirname(relpath), self.registry.directory ) raise FileExistsError( f"File {self.filename} within {directory} already exists in the registry." ) return relpath def _get_filename( self ): """ Get the filename of the file record. """ ids = self.registry.index.id.astype(str) # logger.debug( f"ids={ids}" ) logger.debug( f"self.id={self.id}" ) if str(self.id) in ids: logger.debug( self.registry.index ) logger.debug( ids == str(self.id) ) logger.debug( self.registry.index.loc[ids == str(self.id), "filename"].values[0] ) return self.registry.index.loc[ids == str(self.id), "filename"].values[0] return os.path.basename( self.filename ) def __repr__( self ): return f"{self.__class__.__name__}(id = {self.id}, filename = {self.filename})"