diff --git a/migration/versions/16041bfd2411_initial.py b/migration/versions/16041bfd2411_initial.py deleted file mode 100644 index 6d40ce2..0000000 --- a/migration/versions/16041bfd2411_initial.py +++ /dev/null @@ -1,33 +0,0 @@ -"""initial - -Revision ID: 16041bfd2411 -Revises: -Create Date: 2023-07-13 01:26:56.342068 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '16041bfd2411' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('people', - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('first_name', sa.Text(), nullable=False), - sa.Column('last_name', sa.Text(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('people') - # ### end Alembic commands ### diff --git a/migration/versions/3510de1e1ff1_fix_fk.py b/migration/versions/3510de1e1ff1_fix_fk.py new file mode 100644 index 0000000..980cb59 --- /dev/null +++ b/migration/versions/3510de1e1ff1_fix_fk.py @@ -0,0 +1,32 @@ +"""fix FK + +Revision ID: 3510de1e1ff1 +Revises: 58cc3bbaabd1 +Create Date: 2023-07-16 01:28:47.704514 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3510de1e1ff1' +down_revision = '58cc3bbaabd1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_foreign_key(None, 'addresses', 'persons', ['person_id'], ['id']) + op.create_foreign_key(None, 'contacts', 'persons', ['person_id'], ['id']) + op.create_foreign_key(None, 'documents', 'persons', ['person_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'documents', type_='foreignkey') + op.drop_constraint(None, 'contacts', type_='foreignkey') + op.drop_constraint(None, 'addresses', type_='foreignkey') + # ### end Alembic commands ### diff --git a/migration/versions/58cc3bbaabd1_person_address_contact_document_v1.py b/migration/versions/58cc3bbaabd1_person_address_contact_document_v1.py new file mode 100644 index 0000000..9c5f7c7 --- /dev/null +++ b/migration/versions/58cc3bbaabd1_person_address_contact_document_v1.py @@ -0,0 +1,85 @@ +"""person, address, contact, document v1 + +Revision ID: 58cc3bbaabd1 +Revises: c74556e04b57 +Create Date: 2023-07-16 01:19:02.307879 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '58cc3bbaabd1' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('addresses', + sa.Column('category', sa.String(length=30), nullable=False), + sa.Column('region', sa.String(length=100), nullable=True), + sa.Column('city', sa.String(length=100), nullable=True), + sa.Column('street', sa.String(length=100), nullable=True), + sa.Column('house', sa.String(length=100), nullable=True), + sa.Column('flat', sa.String(length=100), nullable=True), + sa.Column('person_id', sa.UUID(), nullable=False), + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(), nullable=False), + sa.Column('updated_at', sa.TIMESTAMP(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_addresses_id'), 'addresses', ['id'], unique=False) + op.create_table('contacts', + sa.Column('category', sa.String(length=30), nullable=False), + sa.Column('value', sa.String(length=100), nullable=False), + sa.Column('person_id', sa.UUID(), nullable=False), + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(), nullable=False), + sa.Column('updated_at', sa.TIMESTAMP(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_contacts_id'), 'contacts', ['id'], unique=False) + op.create_table('documents', + sa.Column('category', sa.String(length=30), nullable=False), + sa.Column('series', sa.String(length=30), nullable=False), + sa.Column('number', sa.String(length=50), nullable=False), + sa.Column('issued_by', sa.String(length=200), nullable=False), + sa.Column('issued_at', sa.Date(), nullable=True), + sa.Column('person_id', sa.UUID(), nullable=False), + sa.Column('attachments', postgresql.ARRAY(sa.String(length=200)), nullable=True), + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(), nullable=False), + sa.Column('updated_at', sa.TIMESTAMP(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_documents_id'), 'documents', ['id'], unique=False) + op.create_table('persons', + sa.Column('first_name', sa.String(length=100), nullable=False), + sa.Column('last_name', sa.String(length=100), nullable=False), + sa.Column('patronymic', sa.String(length=100), nullable=True), + sa.Column('gender', sa.String(length=50), nullable=True), + sa.Column('birth_date', sa.Date(), nullable=True), + sa.Column('photos', postgresql.ARRAY(sa.String(length=200)), nullable=True), + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(), nullable=False), + sa.Column('updated_at', sa.TIMESTAMP(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_persons_id'), 'persons', ['id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_persons_id'), table_name='persons') + op.drop_table('persons') + op.drop_index(op.f('ix_documents_id'), table_name='documents') + op.drop_table('documents') + op.drop_index(op.f('ix_contacts_id'), table_name='contacts') + op.drop_table('contacts') + op.drop_index(op.f('ix_addresses_id'), table_name='addresses') + op.drop_table('addresses') + # ### end Alembic commands ### diff --git a/notes.txt b/notes.txt index ab9e350..a93b9b1 100644 --- a/notes.txt +++ b/notes.txt @@ -2,6 +2,7 @@ export PYTHONPATH='/home/dd/astra/personal_information/src' export POSTGRES_DB='personal_info_db' docker run -itd -e POSTGRES_USER=barbie -e POSTGRES_PASSWORD=redStar4,1 -p 5432:5432 -v /home/dd/astra/postgresql_db:/var/lib/postgresql/data --name personal_info_db postgres docker exec -it personal_info_db bash +docker start 9a9653ea37ca psql -U barbie -d barbie CREATE DATABASE personal_info_db; @@ -10,6 +11,8 @@ alembic revision --autogenerate -m 'initial' alembic init alembic в alembic.ini правим sqlalchemy.url alembic revision -m "add columns to checkup table" +alembic revision --autogenerate -m "person, address, contact, document v1" + alembic upgrade ae1 (можно часть версии, лишь бы однозначно указывала) diff --git a/src/personal_info/db/models.py b/src/personal_info/db/models.py index 9898f4e..29e6a29 100644 --- a/src/personal_info/db/models.py +++ b/src/personal_info/db/models.py @@ -1,17 +1,76 @@ import uuid from typing import Any +import datetime import sqlalchemy as sa -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.postgresql import UUID, ARRAY from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship +from sqlalchemy.orm import relationship, DeclarativeBase from sqlalchemy.sql.schema import ForeignKey -Base: Any = declarative_base() +#Base: Any = declarative_base() +class Base(DeclarativeBase): + pass -class Person(Base): - __tablename__ = 'people' +class BaseModel(Base): + __abstract__ = True - id = sa.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - first_name = sa.Column(sa.Text, nullable=False) - last_name = sa.Column(sa.Text, nullable=False) \ No newline at end of file + id = sa.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True) + created_at = sa.Column(sa.TIMESTAMP, nullable=False, default=datetime.datetime.now()) + updated_at = sa.Column(sa.TIMESTAMP, nullable=False, default=datetime.datetime.now(), onupdate=datetime.datetime.now()) + + +class Person(BaseModel): + __tablename__ = 'persons' + + first_name = sa.Column(sa.String(100), nullable=False) + last_name = sa.Column(sa.String(100), nullable=False) + patronymic = sa.Column(sa.String(100)) + gender = sa.Column(sa.String(50)) + birth_date = sa.Column(sa.Date) + addresses = relationship('Address', back_populates='person') + contacts = relationship('Contact', back_populates='person') + documents = relationship('Document', back_populates='person') + photos = sa.Column(ARRAY(sa.String(200))) + + def __repr__(self): + return f'Person {self.last_name} {self.first_name} {self.id}' + +class Address(BaseModel): + __tablename__ = 'addresses' + category = sa.Column(sa.String(30), nullable=False) + region = sa.Column(sa.String(100)) + city = sa.Column(sa.String(100)) + street = sa.Column(sa.String(100)) + house = sa.Column(sa.String(100)) + flat = sa.Column(sa.String(100)) + person_id = sa.Column(UUID(as_uuid=True), sa.ForeignKey("persons.id"), nullable=False) + person = relationship('Person', back_populates='addresses') + + def __repr__(self): + return f'Address {self.city} {self.street} {self.id}' + +class Contact(BaseModel): + __tablename__ = 'contacts' + category = sa.Column(sa.String(30), nullable=False) + value = sa.Column(sa.String(100), nullable=False) + person_id = sa.Column(UUID(as_uuid=True), sa.ForeignKey("persons.id"), nullable=False) + person = relationship('Person', back_populates='contacts') + + def __repr__(self): + return f'Contact {self.category} {self.value} {self.id}' + +class Document(BaseModel): + __tablename__ = 'documents' + + category = sa.Column(sa.String(30), nullable=False) + series = sa.Column(sa.String(30), nullable=False) + number = sa.Column(sa.String(50), nullable=False) + issued_by = sa.Column(sa.String(200), nullable=False) + issued_at = sa.Column(sa.Date) + person_id = sa.Column(UUID(as_uuid=True), sa.ForeignKey("persons.id"), nullable=False) + person = relationship('Person', back_populates='documents') + attachments = sa.Column(ARRAY(sa.String(200))) + + def __repr__(self): + return f'Document {self.category} {self.number} {self.id}' \ No newline at end of file diff --git a/src/personal_info/db/schemas/person.py b/src/personal_info/db/schemas/person.py index a5b74d4..570b343 100644 --- a/src/personal_info/db/schemas/person.py +++ b/src/personal_info/db/schemas/person.py @@ -1,20 +1,56 @@ -from datetime import datetime -from typing import List +from datetime import date +from typing import List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from pydantic.types import UUID4 class Person(BaseModel): - id: UUID4 first_name: str last_name: str class Config: - orm_mode = True + orm_mode = True -class PersonCreate(BaseModel): +class PersonBase(BaseModel): first_name: str last_name: str + patronymic: Optional[str] + gender: Optional[str] + birth_date: Optional[date] + + class Config: + validate_assignment=True + revalidate_instances='always' + orm_mode = True + # extra='ignore' + +class AddressCreate(BaseModel): + category: str + region: Optional[str] + city: Optional[str] + street: Optional[str] + house: Optional[str] + flat: Optional[str] + +class ContactCreate(BaseModel): + category: str + value: str + +class DocumentCreate(BaseModel): + category: str + series: str + number: str + issued_by: str + issued_at: Optional[date] + attachments: Optional[List[str]] + +class PersonCreate(PersonBase): + + addresses: Optional[List[AddressCreate]] = [] + contacts: Optional[List[ContactCreate]] = [] + documents: Optional[List[DocumentCreate]] = [] + photos: Optional[List[str]] = [] + class PersonDetail(Person): id: UUID4 diff --git a/src/personal_info/routers/persons.py b/src/personal_info/routers/persons.py index 6bccf09..2bd259c 100644 --- a/src/personal_info/routers/persons.py +++ b/src/personal_info/routers/persons.py @@ -13,8 +13,6 @@ router = APIRouter(prefix='/persons') async def list_persons( persons_service: PersonService = Depends(get_persons_service), ) -> List[models.Person]: - print('=======================================') - print(persons_service.list()) return persons_service.list() @router.get('/{person_id}', response_model=PersonDetail) diff --git a/src/personal_info/services/__init__.py b/src/personal_info/services/__init__.py index 2ea46a8..57565af 100644 --- a/src/personal_info/services/__init__.py +++ b/src/personal_info/services/__init__.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from personal_info.db.session import get_session -from .people import PersonService +from .persons import PersonService def get_persons_service(db_session: Session = Depends(get_session)) -> PersonService: return PersonService(db_session) diff --git a/src/personal_info/services/people.py b/src/personal_info/services/people.py deleted file mode 100644 index b6ff7c3..0000000 --- a/src/personal_info/services/people.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Any - -from sqlalchemy.orm import Session - -from personal_info.db.models import Person -from personal_info.db.schemas import PersonCreate - -from personal_info.services.base import BaseService - -class PersonService(BaseService[Person, PersonCreate, Any]): - def __init__(self, db_session: Session): - super(PersonService, self).__init__(Person, db_session) - -# def create(self, obj: PersonCreate) -> Person: - - # person: Person = Person() - # self.db_session.add(person) - # self.db_session.flush() - # self.db_session.commit() - # return person diff --git a/src/personal_info/services/persons.py b/src/personal_info/services/persons.py new file mode 100644 index 0000000..8e521ef --- /dev/null +++ b/src/personal_info/services/persons.py @@ -0,0 +1,39 @@ +from typing import Any + +from sqlalchemy.orm import Session + +from personal_info.db.models import Person, Address, Document, Contact +from personal_info.db.schemas import PersonCreate, PersonBase + +from personal_info.services.base import BaseService + +class PersonService(BaseService[Person, PersonCreate, Any]): + def __init__(self, db_session: Session): + super(PersonService, self).__init__(Person, db_session) + + def create(self, obj: PersonCreate) -> Person: + print('= raw' * 3 ) + print(obj) + person_obj = PersonBase(**obj.dict()) + person = super(PersonService, self).create(person_obj) + + addresses = [ + Address(**address.dict(), person_id=person.id) for address in obj.addresses + ] + self.db_session.add_all(addresses) + self.db_session.commit() + + documents = [ + Document(**document.dict(), person_id=person.id) for document in obj.documents + ] + self.db_session.add_all(documents) + self.db_session.commit() + + contacts = [ + Contact(**contact.dict(), person_id=person.id) for contact in obj.contacts + ] + self.db_session.add_all(contacts) + self.db_session.commit() + + return person +