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

Add and List Data Providers #17

Merged
merged 25 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Development databases
*.sqlite3
18 changes: 13 additions & 5 deletions nad_ch/application_context.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import os
from .gateways.storage_mock import StorageGatewayMock
from nad_ch.infrastructure.database import (
session_scope,
SqlAlchemyDataProviderRepository
)
from tests.mocks import MockDataProviderRepository


class ApplicationContext:
def __init__(self):
self._storage = StorageGatewayMock()
self._providers = SqlAlchemyDataProviderRepository(session_scope)

@property
def storage(self):
return self._storage
def providers(self):
return self._providers


class TestApplicationContext(ApplicationContext):
def __init__(self):
self._storage = StorageGatewayMock()
self._providers = MockDataProviderRepository()

@property
def providers(self):
return self._providers


def create_app_context():
Expand Down
8 changes: 8 additions & 0 deletions nad_ch/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dotenv import load_dotenv
import os


load_dotenv()


DATABASE_URL = os.getenv('DATABASE_URL')
41 changes: 25 additions & 16 deletions nad_ch/controllers/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import click
from ..entities import File
from ..use_cases import upload_file, list_files, get_file_metadata
from nad_ch.use_cases import (
add_data_provider,
list_data_providers,
ingest_data_submission,
InvalidProviderNameException
)


@click.group()
Expand All @@ -11,28 +15,33 @@ def cli(ctx):

@cli.command()
@click.pass_context
@click.argument('filename')
@click.argument('content')
def upload(ctx, filename, content):
@click.argument('provider_name')
def add_provider(ctx, provider_name):
context = ctx.obj
file = File(name=filename, content=content)
upload_file(context, file)
click.echo(f"Uploaded {filename}")
try:
add_data_provider(context, provider_name)
except InvalidProviderNameException as e:
click.echo(f"Error: {e.message}")
return
danielnaab marked this conversation as resolved.
Show resolved Hide resolved

click.echo('Provider added')


@cli.command()
@click.pass_context
def listall(ctx):
def list_providers(ctx):
context = ctx.obj
files = list_files(context)
for file in files:
click.echo(file.name)
providers = list_data_providers(context)
click.echo('Data Provider Names:')
for p in providers:
click.echo(p.name)
danielnaab marked this conversation as resolved.
Show resolved Hide resolved


@cli.command()
@click.pass_context
@click.argument('filename')
def metadata(ctx, filename):
@click.argument('filepath')
@click.argument('provider')
def ingest(ctx, file_path, provider):
context = ctx.obj
metadata = get_file_metadata(context, filename)
click.echo(f"File: {metadata.name}, Size: {metadata.size} bytes")
ingest_data_submission(context, file_path, provider)
click.echo('Ingest complete')
9 changes: 9 additions & 0 deletions nad_ch/domain/entities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class DataProvider:
def __init__(self, name: str):
self.name = name


class DataSubmission:
def __init__(self, file_path: str, provider: DataProvider):
self.file_path = file_path
self.provider = provider
13 changes: 13 additions & 0 deletions nad_ch/domain/repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import List, Protocol
from nad_ch.domain.entities import DataProvider


class DataProviderRepository(Protocol):
def add(self, provider: DataProvider) -> None:
...

def get_by_name(self, name: str) -> DataProvider:
...

def get_all(self) -> List[DataProvider]:
danielnaab marked this conversation as resolved.
Show resolved Hide resolved
...
13 changes: 0 additions & 13 deletions nad_ch/entities.py

This file was deleted.

25 changes: 0 additions & 25 deletions nad_ch/gateways/storage_mock.py

This file was deleted.

File renamed without changes.
67 changes: 67 additions & 0 deletions nad_ch/infrastructure/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import List
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
import contextlib
from nad_ch.config import DATABASE_URL
from nad_ch.domain.entities import DataProvider
from nad_ch.domain.repositories import DataProviderRepository


engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)


@contextlib.contextmanager
def session_scope():
session = Session()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()


ModelBase = declarative_base()


class DataProviderModel(ModelBase):
__tablename__ = 'data_providers'

id = Column(Integer, primary_key=True)
name = Column(String)

@staticmethod
def from_entity(provider):
return DataProviderModel(name=provider.name)

def to_entity(self):
return DataProvider(name=self.name)


class SqlAlchemyDataProviderRepository(DataProviderRepository):
def __init__(self, session_factory):
self.session_factory = session_factory

def add(self, provider: DataProvider):
with self.session_factory() as session:
provider_model = DataProviderModel.from_entity(provider)
session.add(provider_model)
return provider_model.to_entity()

def get_by_name(self, name: str) -> DataProvider:
with self.session_factory() as session:
provider_model = (
session.query(DataProviderModel)
.filter(DataProviderModel.name == name)
.first()
)
return provider_model.to_entity()

def get_all(self) -> List[DataProvider]:
with self.session_factory() as session:
provider_models = session.query(DataProviderModel).all()
providers_entities = [provider.to_entity() for provider in provider_models]
return providers_entities
File renamed without changes.
13 changes: 0 additions & 13 deletions nad_ch/interfaces/storage.py

This file was deleted.

4 changes: 2 additions & 2 deletions nad_ch/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .controllers.cli import cli
from .application_context import create_app_context
from nad_ch.controllers.cli import cli
from nad_ch.application_context import create_app_context


def main():
Expand Down
37 changes: 25 additions & 12 deletions nad_ch/use_cases.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
from .application_context import ApplicationContext
from .entities import File, FileMetadata
from .interfaces.storage import StorageGateway
from nad_ch.application_context import ApplicationContext
from nad_ch.domain.entities import DataProvider


def upload_file(ctx: ApplicationContext, file: File) -> None:
storage: StorageGateway = ctx.storage
storage.save(file)
def add_data_provider(
ctx: ApplicationContext, provider_name: str
) -> None:
if not provider_name:
raise InvalidProviderNameException()

provider = DataProvider(provider_name)
ctx.providers.add(provider)

def list_files(ctx: ApplicationContext) -> list[File]:
storage: StorageGateway = ctx.storage
return storage.list_all()

def list_data_providers(
ctx: ApplicationContext
):
list = ctx.providers.get_all()
return list

def get_file_metadata(ctx: ApplicationContext, file_name: str) -> FileMetadata:
storage: StorageGateway = ctx.storage
return storage.get_metadata(file_name)

def ingest_data_submission(
ctx: ApplicationContext, file_path: str, provider_name: str
) -> None:
pass


class InvalidProviderNameException(Exception):
def __init__(self, message='Provider name is required'):
self.message = message
super().__init__(self.message)
Loading
Loading