app.py:
```
from database import crud

# Start with os.walk
import os
from datetime import datetime

async def test_main():
    async with crud.AsyncSessionLocal() as db:

        for dirpath, dirnames, filenames in os.walk(R'G:\_AI\training data\Concepts'):
            print(f"Found directory: {dirpath}")

            for filename in filenames:
                print(f"File inside {dirpath}: {filename}")

                # DB Test
                full_path = os.path.join(dirpath, filename)
                file_stats = os.stat(full_path)
                file_size = file_stats.st_size
                file_created_at = datetime.fromtimestamp(file_stats.st_ctime)
                file_updated_at = datetime.fromtimestamp(file_stats.st_mtime)
                file_type = 'unknown'

                # Calling the CRUD function to add the file to the database
                db_file = await crud.create_file(
                    db,
                    path=full_path,
                    file_type=file_type,
                    size=file_size,
                    file_created_at=file_created_at,
                    file_updated_at=file_updated_at
                )

                print(f"Added to database: {db_file.path}")


import asyncio
if __name__ == '__main__':
    crud.create_tables()

    asyncio.run(test_main())
```

database\database.py:
```
from database import crud

# Start with os.walk
import os
from datetime import datetime

async def test_main():
    async with crud.AsyncSessionLocal() as db:

        for dirpath, dirnames, filenames in os.walk(R'G:\_AI\training data\Concepts'):
            print(f"Found directory: {dirpath}")

            for filename in filenames:
                print(f"File inside {dirpath}: {filename}")

                # DB Test
                full_path = os.path.join(dirpath, filename)
                file_stats = os.stat(full_path)
                file_size = file_stats.st_size
                file_created_at = datetime.fromtimestamp(file_stats.st_ctime)
                file_updated_at = datetime.fromtimestamp(file_stats.st_mtime)
                file_type = 'unknown'

                # Calling the CRUD function to add the file to the database
                db_file = await crud.create_file(
                    db,
                    path=full_path,
                    file_type=file_type,
                    size=file_size,
                    file_created_at=file_created_at,
                    file_updated_at=file_updated_at
                )

                print(f"Added to database: {db_file.path}")


import asyncio
if __name__ == '__main__':
    crud.create_tables()

    asyncio.run(test_main())
```

database\model.py:
```
from sqlalchemy import DateTime, func, String, Integer, ForeignKey, UniqueConstraint
from sqlalchemy.orm import declarative_base, Mapped, mapped_column, relationship
from typing import List

# Core Model
Model = declarative_base()

class TimeStampedModel(Model):
    """
    Tell my dumbass I missed a docstring.
    """
    __abstract__ = True

    created_at = mapped_column(DateTime(timezone=True), default=func.now())
    updated_at = mapped_column(DateTime(timezone=True), default=func.now(), onupdate=func.now())

"""
TEMPLATE:

class (TimeStampedModel):
"""
"""
    __tablename__ = ""

    # columns

    # relationships

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(id={self.id!r})"
"""

class Fingerprint(TimeStampedModel):
    """
    Tell my dumbass I missed a docstring.
    """
    __tablename__ = "fingerprints"

    # columns
    fingerprint: Mapped[str] = mapped_column(String(128), primary_key=True)
    size: Mapped[int] = mapped_column(Integer)
    
    __table_args__ = (
        UniqueConstraint('fingerprint', 'size'),
    )

    # relationships
    files: Mapped[List['FingerprintFile']] = relationship("FingerprintFile", back_populates="fingerprint_relation", cascade="all, delete")

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(fingerprint={self.fingerprint!r}, size={self.size!r})"

class HashType(TimeStampedModel):
    """
    Tell my dumbass I missed a docstring.
    """
    __tablename__ = "hash_types"

    # columns
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(50))

    # relationships

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(id={self.id!r}, name={self.name!r})"

class Hash(TimeStampedModel):
    """
    Tell my dumbass I missed a docstring.
    """
    __tablename__ = "hashes"

    # columns
    fingerprint: Mapped[str] = mapped_column(ForeignKey(Fingerprint.fingerprint), primary_key=True)
    hash_type_id: Mapped[int] = mapped_column(ForeignKey(HashType.id))
    hash: Mapped[str] = mapped_column(String(256), index=True)

    __table_args__ = (
        UniqueConstraint('fingerprint', 'hash_type_id'),
    )

    # relationships

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(fingerprint={self.fingerprint!r}, hash_type_id={self.hash_type_id!r}, hash={self.hash!r})"


class File(TimeStampedModel):
    """
    Tell my dumbass I missed a docstring.
    """
    __tablename__ = "files"

    # columns
    id: Mapped[int] = mapped_column(primary_key=True)
    path: Mapped[str] = mapped_column(index=True, unique=True)
    type: Mapped[str] = mapped_column(String(100), index=True) # We want to store MIME typing here
    size: Mapped[int] = mapped_column(Integer)
    file_created_at = mapped_column(DateTime(timezone=True))
    file_updated_at = mapped_column(DateTime(timezone=True))

    # relationships
    fingerprint: Mapped['FingerprintFile'] = relationship("FingerprintFile", back_populates="file_relation", cascade="all, delete-orphan")

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(id={self.id!r}, path=`{self.path!r}`, type={self.type!r}, size={self.size!r}, file_created_at={self.file_created_at!r}, file_updated_at={self.file_updated_at!r})"


class FingerprintFile(TimeStampedModel):
    """
    Association table for linking fingerprints to files.
    This table supports a one-to-many relationship between Fingerprint and File.
    Each Fingerprint might be related to multiple Files, but each File can be associated
    with only a specific Fingerprint at any given time.
    """
    __tablename__ = "fingerprint_files"

    # columns
    fingerprint: Mapped[str] = mapped_column(ForeignKey(Fingerprint.fingerprint), primary_key=True)
    file_id: Mapped[int] = mapped_column(ForeignKey(File.id), primary_key=True)

    # relationships
    fingerprint_relation: Mapped['Fingerprint'] = relationship("Fingerprint", back_populates="files")
    file_relation: Mapped['File'] = relationship("File", back_populates="fingerprint")

    # methods
    def __repr__(self):
        return f"{self.__tablename__!r}(fingerprint={self.fingerprint!r}, file_id={self.file_id!r})"
```

database\crud.py:
```
# Database CRUD
from sqlalchemy import DateTime, select, delete
from sqlalchemy.ext.asyncio import AsyncSession

from .database import sync_engine, AsyncSessionLocal
from .model import Model, File, Fingerprint, FingerprintFile

"""
Global crud
"""


# Global DB access
def get_db():
    db = AsyncSessionLocal()
    try:
        yield db
    finally:
        db.close()

# This cannot be done async
def create_tables():
    Model.metadata.create_all(sync_engine)


"""
Fingerprint
"""

# CREATE
async def create_fingerprint(db: AsyncSession, fingerprint: str, size: int):
    new_fingerprint = Fingerprint(fingerprint=fingerprint, size=size)

    db.add(new_fingerprint)
    await db.commit()

    return new_fingerprint

# READ
async def fetch_fingerprint(db: AsyncSession, fingerprint: str):
    result = await db.execute(
        select(Fingerprint).where(Fingerprint.fingerprint == fingerprint)
    )

    return result.scalars().first()

# UPDATE
async def update_fingerprint(db: AsyncSession, fingerprint: str, new_size: int):
    db_fingerprint = await fetch_fingerprint(db, fingerprint)

    if db_fingerprint:
        db_fingerprint.size = new_size
        await db.commit()
        return db_fingerprint
    
    return None

# DELETE
async def delete_fingerprint(db: AsyncSession, fingerprint):
    db_fingerprint = await fetch_fingerprint(db, fingerprint)

    if db_fingerprint:
        await db.delete(db_fingerprint)
        await db.commit()
        return True
    
    return False


"""
File
"""

# TODO: Use this as an example to docstring the rest of the project.
# TODO: Also: `Readme and Guides: Maintain a README.md for your repository, outlining project setup, how to install dependencies, how to run the project, and other usage notes.`
# CREATE
async def create_file(db: AsyncSession, path: str, file_type: str, size: int, file_created_at: DateTime, file_updated_at: DateTime):
    """
    Creates a new file record in the database.
    
    Parameters:
        db (Session): The database session.
        path (str): The file path.
        file_type (str): The type of the file.
        size (int): The size of the file in bytes.
        file_created_at (datetime): When the file was created.
        file_updated_at (datetime): When the file was last updated.

    Returns:
        File: The created File object.
    """
    # Check if the entry already exists in the database
    result = await db.execute(
        select(File).where(File.path == path)
    )

    db_file = result.scalars().first()

    if db_file:
        return db_file

    new_file = File(
        path=path,
        type=file_type,
        size=size,
        file_created_at=file_created_at,
        file_updated_at=file_updated_at
    )

    db.add(new_file)
    await db.commit()
    await db.refresh(new_file)

    return new_file

# READ
async def fetch_file(db: AsyncSession, file_id: int):
    result = await db.execute(
        select(File).where(File.id == file_id)
    )

    return result.scalars().first()

# UPDATE
async def update_file(db: AsyncSession, file_id: int, path: str = None, file_type: str = None, size: int = None, file_created_at: DateTime = None, file_updated_at: DateTime = None):
    db_file = await fetch_file(db, file_id)

    if db_file:
        if path:
            db_file.path = path
        if file_type:
            db_file.type = file_type
        if size:
            db_file.size = size
        if file_created_at:
            db_file.file_created_at = file_created_at
        if file_updated_at:
            db_file.file_updated_at = file_updated_at

        await db.commit()
        return db_file
    
    return None

# DELETE
async def delete_file(db: AsyncSession, file_id: int):
    db_file = await fetch_file(db, file_id)

    if db_file:
        await db.delete(db_file)
        await db.commit()
        return True
    
    return False

"""
Fingerprint and File Relationship
"""

# CREATE
async def associate_fingerprint_and_file(db: AsyncSession, fingerprint: str, file_id: int):
    existing_association = await db.execute(
        select(FingerprintFile).where(FingerprintFile.fingerprint == fingerprint, FingerprintFile.file_id == file_id)
    )

    if existing_association.scalars().first():
        return "Association already exists."
    
    association = FingerprintFile(fingerprint=fingerprint, file_id=file_id)
    db.add(association)
    await db.commit()

    return "Association created."

# READ
async def fetch_files_by_fingerprint(db: AsyncSession, fingerprint: str):
    result = await db.execute(
        select(File).join(FingerprintFile).where(FingerprintFile.fingerprint == fingerprint)
    )

    return result.scalars().all()

# UPDATE OR CREATE
async def update_or_create_fingerprint(db: AsyncSession, file_id: int, new_fingerprint_hash: str, size: int):
    # Starting a transaction
    async with db.begin():
        # Check if the new fingerprint already exists
        new_fingerprint = await db.get(Fingerprint, new_fingerprint_hash)

        if not new_fingerprint:
            new_fingerprint = Fingerprint(fingerprint=new_fingerprint_hash, size=size)
            db.add(new_fingerprint)
            await db.commit()

        # Get the file and update its fingerprint association
        file = await db.get(File, file_id)

        if not file:
            return "404 - File not found."
        
        # Remove old associations (if any)
        await db.execute(
            delete(FingerprintFile).where(FingerprintFile.file_id == file_id)
        )

        # Create new association
        new_association = FingerprintFile(fingerprint=new_fingerprint_hash, file_id=file_id)
        db.add(new_association)
        await db.commit()

    return "Fingerprint updated."

# DELETE
async def remove_association_fingerprint_and_file(db: AsyncSession, fingerprint: str, file_id: int):
    association = await db.execute(
        select(FingerprintFile).where(FingerprintFile.fingerprint == fingerprint, FingerprintFile.file_id == file_id)
    )

    association_to_remove = association.scalars().first()

    if association_to_remove:
        await db.delete(association_to_remove)
        await db.commit()
        return "Association removed."
    
    return "Association not found."
```

Above is the files I have currently in my project, I think the core functionality is there, however I want to you to a deep dive, and offer step-by-step instruction and information on the next steps for what I want to do:
- The File tables type field needs to take in the MIME type of the file being added.
- I want to start a background thread that starts scanning folders and adding files to the database using os.walk.
- For every file I want a separate background thread to handle hashing the Fingerprint table(using a SHA512 hash), and running through other hashing algorithms for each file to add the hashes to the database, it should be flexible to easily add other hashing algorithms to be calculated later, including custom ones, each hash needs an entry in HashType table.
- I also need Files linked to the Fingerprint(one fingerprint, many files) using FingerprintFile table.
- I want progress bars using tqdm.
- And finally, I want this to use industry standard, with the most modern python best practices.