Skip to content

Commit

Permalink
Add client table (#158)
Browse files Browse the repository at this point in the history
* Add client table (#148)
Add clientSQL in splmodels, with client_name
Mapped relationships between Client and Site

* Alembic migration : Add client table (#148)

* Fix: make client_uuid nullable in Site table (#148)

* Add read function get_sites_by_client_name and tests (#148)
add function, tests, and edited sites fixture in conftest in order to have client informations in tests samples

* update readme Database schema (#148)

* Add create_client and edit_client functions (#148)
added create_client and edit_client functions, and related tests.
edited PVSiteEditMetadata pydantic model, so edit_site function can update the client_uuid.
edited create_site function, so it can set the client_uuid.

* Move create_client and edit_client to a new file (#148)
Moved the client functions from user_and_site.py to client.py

* Add get_client_by_name function (#148)
Added get_client_by_name function, and related tests. This function can create a new client if it doesn't exist.

* Add assign_site_to_client function (#148)

* Adjust alembic migration (#148)

---------

Co-authored-by: Peter Dudfield <[email protected]>
  • Loading branch information
Bvr4 and peterdudfield authored Oct 13, 2024
1 parent f726172 commit 720720b
Show file tree
Hide file tree
Showing 15 changed files with 316 additions and 3 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Classes specifying table schemas:
- SiteSQL
- SiteGroupSQL
- StatusSQL
- ClientSQL
Database connection objects:
- DatabaseConnection
Expand All @@ -54,11 +55,13 @@ Currently available functions accessible via `from pvsite_datamodel.read import
- get_site_by_uuid
- get_site_by_client_site_id
- get_site_by_client_site_name
- get_sites_by_client_name
- get_all_sites
- get_sites_by_country
- get_site_group_by_name
- get_latest_status
- get_latest_forecast_values_by_site
- get_client_by_name


### Write package functions
Expand All @@ -77,6 +80,9 @@ Currently available write functions accessible via `from pvsite_datamodels.write
- delete_user
- delete_site_group
- make_fake_site
- create_client
- edit_client
- assign_site_to_client


## Install the dependencies (requires [poetry][poetry])
Expand Down Expand Up @@ -140,7 +146,14 @@ classDiagram
+ inverter_capacity_kw : Float
+ module_capacity_kw : Float
+ ml_id : Integer ≪ U ≫
+ client_uuid : UUID ≪ FK ≫
}
class ClientSQL{
+ client_uuid : UUID ≪ PK ≫
+ client_name : String(255)
}
class GenerationSQL{
+ generation_uuid : UUID ≪ PK ≫
+ site_uuid : UUID ≪ FK ≫
Expand Down Expand Up @@ -197,6 +210,7 @@ classDiagram
MLModelSQL "1" -- "N" ForecastValueSQL : forecasts
SiteSQL "1" -- "N" InverterSQL : contains
UserSQL "1" -- "N" APIRequestSQL : performs_request
ClientSQL "1" -- "N" SiteSQL : owns
class Legend{
UUID: Universally Unique Identifier
PK: Primary Key
Expand Down
41 changes: 41 additions & 0 deletions alembic/versions/31d501a0aa52_add_client_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Add client table
Revision ID: 31d501a0aa52
Revises: 2a6e6975cd72
Create Date: 2024-09-13 11:54:12.743980
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '31d501a0aa52'
down_revision = '2a6e6975cd72'
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('clients',
sa.Column('client_uuid', sa.UUID(), nullable=False),
sa.Column('client_name', sa.String(length=255), nullable=False),
sa.Column('created_utc', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('client_uuid')
)
op.create_index(op.f('ix_clients_client_name'), 'clients', ['client_name'], unique=True)
op.add_column('sites', sa.Column('client_uuid', sa.UUID(), nullable=True, comment='The UUID of the client this site belongs to'))
op.create_index(op.f('ix_sites_client_uuid'), 'sites', ['client_uuid'], unique=False)
op.create_foreign_key(None, 'sites', 'clients', ['client_uuid'], ['client_uuid'])
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'sites', type_='foreignkey')
op.drop_index(op.f('ix_sites_client_uuid'), table_name='sites')
op.drop_column('sites', 'client_uuid')
op.drop_index(op.f('ix_clients_client_name'), table_name='clients')
op.drop_table('clients')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions pvsite_datamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .connection import DatabaseConnection
from .sqlmodels import (
APIRequestSQL,
ClientSQL,
ForecastSQL,
ForecastValueSQL,
GenerationSQL,
Expand Down
4 changes: 4 additions & 0 deletions pvsite_datamodel/pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from datetime import datetime
from typing import Optional
from uuid import UUID

from pydantic import BaseModel, Field

Expand Down Expand Up @@ -63,3 +64,6 @@ class PVSiteEditMetadata(BaseModel):
capacity_kw: Optional[float] = Field(None, description="The site's total capacity in kw", ge=0)
dno: Optional[str] = Field(None, description="The site's DNO")
gsp: Optional[str] = Field(None, description="The site's GSP")
client_uuid: Optional[UUID] = Field(
None, description="The UUID of the client this site belongs to"
)
2 changes: 2 additions & 0 deletions pvsite_datamodel/read/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Functions for reading from the PVSite database
"""

from .client import get_client_by_name
from .generation import get_pv_generation_by_sites, get_pv_generation_by_user_uuids
from .latest_forecast_values import get_latest_forecast_values_by_site
from .model import get_or_create_model
Expand All @@ -10,6 +11,7 @@
get_site_by_client_site_id,
get_site_by_client_site_name,
get_site_by_uuid,
get_sites_by_client_name,
get_sites_by_country,
get_sites_from_user,
)
Expand Down
37 changes: 37 additions & 0 deletions pvsite_datamodel/read/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
""" Functions for reading user data from the database. """

import logging

from sqlalchemy.orm import Session

from pvsite_datamodel.sqlmodels import ClientSQL

logger = logging.getLogger(__name__)


def get_client_by_name(
session: Session, name: str, make_new_client_if_none: bool = True
) -> ClientSQL:
"""
Get client by name. If client does not exist, make one.
:param session: database session
:param name: name of the client
:param make_new_client_if_none: make client with name if doesn't exist
:return: client object
"""

client = session.query(ClientSQL).filter(ClientSQL.client_name == name).first()

if client is None:
if make_new_client_if_none is True:
logger.info(f"Client with name {name} not found, so making one")

# make a new client
client = ClientSQL(client_name=name)
session.add(client)
session.commit()
else:
raise Exception(f"Could not find client with name {name}")

return client
32 changes: 31 additions & 1 deletion pvsite_datamodel/read/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sqlalchemy.orm import Session

from pvsite_datamodel.pydantic_models import LatitudeLongitudeLimits
from pvsite_datamodel.sqlmodels import SiteGroupSiteSQL, SiteGroupSQL, SiteSQL, UserSQL
from pvsite_datamodel.sqlmodels import ClientSQL, SiteGroupSiteSQL, SiteGroupSQL, SiteSQL, UserSQL

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -170,3 +170,33 @@ def get_sites_from_user(session, user, lat_lon_limits: Optional[LatitudeLongitud
else:
sites = user.site_group.sites
return sites


def get_sites_by_client_name(session: Session, client_name: str) -> List[SiteSQL]:
"""Get sites from client name.
:param session: database session
:param client_name: client name
:return: list of site objects
"""
logger.debug(f"Getting {client_name}'s sites")

# start main query
query = session.query(SiteSQL)

# join the Client table
query = query.join(ClientSQL)

# order by uuuid
query = query.order_by(SiteSQL.site_uuid)

# select the sites related to the client name
query = query.filter(ClientSQL.client_name == client_name)

# get all results
sites: Optional[List[SiteSQL]] = query.all()

if len(sites) == 0:
raise Exception(f"Could not find sites from client {client_name}")

return sites
23 changes: 23 additions & 0 deletions pvsite_datamodel/sqlmodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ class SiteSQL(Base, CreatedMixin):
comment="Auto-incrementing integer ID of the site for use in ML training",
)

client_uuid = sa.Column(
UUID(as_uuid=True),
sa.ForeignKey("clients.client_uuid"),
nullable=True,
index=True,
comment="The UUID of the client this site belongs to",
)

forecasts: Mapped[List["ForecastSQL"]] = relationship("ForecastSQL", back_populates="site")
generation: Mapped[List["GenerationSQL"]] = relationship("GenerationSQL")
inverters: Mapped[List["InverterSQL"]] = relationship(
Expand All @@ -176,6 +184,21 @@ class SiteSQL(Base, CreatedMixin):
site_groups: Mapped[List["SiteGroupSQL"]] = relationship(
"SiteGroupSQL", secondary="site_group_sites", back_populates="sites"
)
client: Mapped[List["ClientSQL"]] = relationship("ClientSQL", back_populates="sites")


class ClientSQL(Base, CreatedMixin):
"""Class representing the client table.
Each client row specifies a single client.
"""

__tablename__ = "clients"

client_uuid = sa.Column(UUID(as_uuid=True), default=uuid.uuid4, primary_key=True)
client_name = sa.Column(sa.String(255), nullable=False, index=True, unique=True)

sites: Mapped[List[SiteSQL]] = relationship("SiteSQL", back_populates="client")


class GenerationSQL(Base, CreatedMixin):
Expand Down
2 changes: 2 additions & 0 deletions pvsite_datamodel/write/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Functions for writing to the PVSite database
"""

from .client import assign_site_to_client, create_client, edit_client
from .forecast import insert_forecast_values
from .generation import insert_generation_values
from .user_and_site import (
Expand All @@ -13,6 +14,7 @@
delete_site,
delete_site_group,
delete_user,
edit_site,
make_fake_site,
update_user_site_group,
)
65 changes: 65 additions & 0 deletions pvsite_datamodel/write/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Tools for making clients in the database.
"""

import logging
from uuid import UUID

from sqlalchemy.orm.session import Session

from pvsite_datamodel.sqlmodels import ClientSQL, SiteSQL

_log = logging.getLogger(__name__)


def create_client(session: Session, client_name: str) -> ClientSQL:
"""Create a client.
:param session: database session
:param client_name: name of client being created
"""
client = ClientSQL(client_name=client_name)

session.add(client)
session.commit()

return client


def edit_client(session: Session, client_uuid: UUID, client_name: str) -> ClientSQL:
"""Edit an existing client.
:param session: database session
:param client_uuid: the existing client uuid
:param client_name: name of the client
"""
client = session.query(ClientSQL).filter(ClientSQL.client_uuid == client_uuid).first()

client.client_name = client_name

session.add(client)
session.commit()

return client


def assign_site_to_client(session: Session, site_uuid: str, client_name: str) -> str:
"""Assign site to client.
:param session: database session
:param site_uuid: uuid of site
:param client_name: name of the client the site will be assigned to.
"""

client = session.query(ClientSQL).filter(ClientSQL.client_name == client_name).first()

site = session.query(SiteSQL).filter(SiteSQL.site_uuid == site_uuid).first()

site.client_uuid = client.client_uuid

session.add(site)
session.commit()

message = f"Site with site uuid {site_uuid} successfully assigned to the client {client_name}"

return message
4 changes: 4 additions & 0 deletions pvsite_datamodel/write/user_and_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
from datetime import datetime, timezone
from typing import Optional, Tuple
from uuid import UUID

import sqlalchemy as sa
from sqlalchemy.orm.session import Session
Expand Down Expand Up @@ -74,6 +75,7 @@ def create_site(
tilt: Optional[float] = None,
inverter_capacity_kw: Optional[float] = None,
module_capacity_kw: Optional[float] = None,
client_uuid: Optional[UUID] = None,
ml_id: Optional[int] = None,
) -> [SiteSQL, str]:
"""
Expand Down Expand Up @@ -150,6 +152,7 @@ def create_site(
tilt=tilt,
inverter_capacity_kw=inverter_capacity_kw,
module_capacity_kw=module_capacity_kw,
client_uuid=client_uuid,
)

session.add(site)
Expand Down Expand Up @@ -269,6 +272,7 @@ def edit_site(
- capacity_kw: capacity of site in kw
- dno: dno of site
- gsp: gsp of site
- client_uuid: The UUID of the client this site belongs to
"""
site = session.query(SiteSQL).filter(SiteSQL.site_uuid == site_uuid).first()

Expand Down
Loading

0 comments on commit 720720b

Please sign in to comment.