Spaces:
Runtime error
Runtime error
initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .coverage +0 -0
- Dockerfile +29 -0
- __pycache__/main.cpython-310.pyc +0 -0
- alembic.ini +100 -0
- alembic/README +1 -0
- alembic/__pycache__/env.cpython-310.pyc +0 -0
- alembic/env.py +80 -0
- alembic/script.py.mako +25 -0
- alembic/versions/__pycache__/5c89a726934c_add_item_migration.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/7e09fa75df7a_add_initial_migration.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/98ffdabb3a7a_adding_docs.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/ae0b63479948_init_db.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/afef0f70285c_adding_oauth_support.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/d8d827ad33ee_init_db.cpython-310.pyc +0 -0
- alembic/versions/__pycache__/db6822e47425_init_db.cpython-310.pyc +0 -0
- alembic/versions/d8d827ad33ee_init_db.py +66 -0
- app/__pycache__/db.cpython-310.pyc +0 -0
- app/__pycache__/factory.cpython-310.pyc +0 -0
- app/api/__init__.py +10 -0
- app/api/__pycache__/__init__.cpython-310.pyc +0 -0
- app/api/__pycache__/documents.cpython-310.pyc +0 -0
- app/api/__pycache__/queries.cpython-310.pyc +0 -0
- app/api/__pycache__/users.cpython-310.pyc +0 -0
- app/api/__pycache__/utils.cpython-310.pyc +0 -0
- app/api/documents.py +75 -0
- app/api/queries.py +92 -0
- app/api/users.py +30 -0
- app/api/utils.py +17 -0
- app/core/__pycache__/config.cpython-310.pyc +0 -0
- app/core/__pycache__/logger.cpython-310.pyc +0 -0
- app/core/config.py +52 -0
- app/core/logger.py +4 -0
- app/db.py +20 -0
- app/deps/__pycache__/db.cpython-310.pyc +0 -0
- app/deps/__pycache__/users.cpython-310.pyc +0 -0
- app/deps/db.py +11 -0
- app/deps/qdrant.py +5 -0
- app/deps/request_params.py +46 -0
- app/deps/users.py +51 -0
- app/email/send_mail.py +2 -0
- app/factory.py +116 -0
- app/models/__init__.py +3 -0
- app/models/__pycache__/__init__.cpython-310.pyc +0 -0
- app/models/__pycache__/document.cpython-310.pyc +0 -0
- app/models/__pycache__/user.cpython-310.pyc +0 -0
- app/models/document.py +27 -0
- app/models/user.py +44 -0
- app/openai/__pycache__/base.cpython-310.pyc +0 -0
- app/openai/__pycache__/core.cpython-310.pyc +0 -0
- app/openai/base.py +82 -0
.coverage
ADDED
Binary file (53.2 kB). View file
|
|
Dockerfile
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ARG PYTHON_VER=3.10
|
2 |
+
|
3 |
+
FROM python:${PYTHON_VER} AS base
|
4 |
+
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
ENV PYTHONUNBUFFERED=1
|
8 |
+
|
9 |
+
# Install Poetry
|
10 |
+
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
|
11 |
+
cd /usr/local/bin && \
|
12 |
+
ln -s /opt/poetry/bin/poetry && \
|
13 |
+
poetry config virtualenvs.create false
|
14 |
+
|
15 |
+
COPY ./pyproject.toml ./poetry.lock* /app/
|
16 |
+
|
17 |
+
RUN poetry install --no-root
|
18 |
+
|
19 |
+
COPY . /app
|
20 |
+
|
21 |
+
FROM python:3.10-slim
|
22 |
+
|
23 |
+
WORKDIR /app
|
24 |
+
|
25 |
+
COPY --from=base /app /app
|
26 |
+
COPY --from=base /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
27 |
+
COPY --from=base /usr/local/bin /usr/local/bin
|
28 |
+
|
29 |
+
CMD uvicorn --host 0.0.0.0 --port 7860 main:app
|
__pycache__/main.cpython-310.pyc
ADDED
Binary file (485 Bytes). View file
|
|
alembic.ini
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# A generic, single database configuration.
|
2 |
+
|
3 |
+
[alembic]
|
4 |
+
# path to migration scripts
|
5 |
+
script_location = alembic
|
6 |
+
|
7 |
+
# template used to generate migration files
|
8 |
+
# file_template = %%(rev)s_%%(slug)s
|
9 |
+
|
10 |
+
# sys.path path, will be prepended to sys.path if present.
|
11 |
+
# defaults to the current working directory.
|
12 |
+
prepend_sys_path = .
|
13 |
+
|
14 |
+
# timezone to use when rendering the date within the migration file
|
15 |
+
# as well as the filename.
|
16 |
+
# If specified, requires the python-dateutil library that can be
|
17 |
+
# installed by adding `alembic[tz]` to the pip requirements
|
18 |
+
# string value is passed to dateutil.tz.gettz()
|
19 |
+
# leave blank for localtime
|
20 |
+
# timezone =
|
21 |
+
|
22 |
+
# max length of characters to apply to the
|
23 |
+
# "slug" field
|
24 |
+
# truncate_slug_length = 40
|
25 |
+
|
26 |
+
# set to 'true' to run the environment during
|
27 |
+
# the 'revision' command, regardless of autogenerate
|
28 |
+
# revision_environment = false
|
29 |
+
|
30 |
+
# set to 'true' to allow .pyc and .pyo files without
|
31 |
+
# a source .py file to be detected as revisions in the
|
32 |
+
# versions/ directory
|
33 |
+
# sourceless = false
|
34 |
+
|
35 |
+
# version location specification; This defaults
|
36 |
+
# to alembic/versions. When using multiple version
|
37 |
+
# directories, initial revisions must be specified with --version-path.
|
38 |
+
# The path separator used here should be the separator specified by "version_path_separator"
|
39 |
+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
40 |
+
|
41 |
+
# version path separator; As mentioned above, this is the character used to split
|
42 |
+
# version_locations. Valid values are:
|
43 |
+
#
|
44 |
+
# version_path_separator = :
|
45 |
+
# version_path_separator = ;
|
46 |
+
# version_path_separator = space
|
47 |
+
version_path_separator = os # default: use os.pathsep
|
48 |
+
|
49 |
+
# the output encoding used when revision files
|
50 |
+
# are written from script.py.mako
|
51 |
+
# output_encoding = utf-8
|
52 |
+
|
53 |
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
54 |
+
|
55 |
+
|
56 |
+
[post_write_hooks]
|
57 |
+
# post_write_hooks defines scripts or Python functions that are run
|
58 |
+
# on newly generated revision scripts. See the documentation for further
|
59 |
+
# detail and examples
|
60 |
+
|
61 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
62 |
+
# hooks = black
|
63 |
+
# black.type = console_scripts
|
64 |
+
# black.entrypoint = black
|
65 |
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
66 |
+
|
67 |
+
# Logging configuration
|
68 |
+
[loggers]
|
69 |
+
keys = root,sqlalchemy,alembic
|
70 |
+
|
71 |
+
[handlers]
|
72 |
+
keys = console
|
73 |
+
|
74 |
+
[formatters]
|
75 |
+
keys = generic
|
76 |
+
|
77 |
+
[logger_root]
|
78 |
+
level = WARN
|
79 |
+
handlers = console
|
80 |
+
qualname =
|
81 |
+
|
82 |
+
[logger_sqlalchemy]
|
83 |
+
level = WARN
|
84 |
+
handlers =
|
85 |
+
qualname = sqlalchemy.engine
|
86 |
+
|
87 |
+
[logger_alembic]
|
88 |
+
level = INFO
|
89 |
+
handlers =
|
90 |
+
qualname = alembic
|
91 |
+
|
92 |
+
[handler_console]
|
93 |
+
class = StreamHandler
|
94 |
+
args = (sys.stderr,)
|
95 |
+
level = NOTSET
|
96 |
+
formatter = generic
|
97 |
+
|
98 |
+
[formatter_generic]
|
99 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
100 |
+
datefmt = %H:%M:%S
|
alembic/README
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Generic single-database configuration.
|
alembic/__pycache__/env.cpython-310.pyc
ADDED
Binary file (1.86 kB). View file
|
|
alembic/env.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from logging.config import fileConfig
|
2 |
+
|
3 |
+
from alembic import context
|
4 |
+
from sqlalchemy import engine_from_config, pool
|
5 |
+
|
6 |
+
# models import is necessary for autogenerate to work
|
7 |
+
from app import models
|
8 |
+
from app.core.config import settings
|
9 |
+
from app.db import Base
|
10 |
+
|
11 |
+
# this is the Alembic Config object, which provides
|
12 |
+
# access to the values within the .ini file in use.
|
13 |
+
config = context.config
|
14 |
+
|
15 |
+
# Interpret the config file for Python logging.
|
16 |
+
# This line sets up loggers basically.
|
17 |
+
fileConfig(config.config_file_name)
|
18 |
+
|
19 |
+
# add your model's MetaData object here
|
20 |
+
# for 'autogenerate' support
|
21 |
+
# from myapp import mymodel
|
22 |
+
# target_metadata = mymodel.Base.metadata
|
23 |
+
target_metadata = Base.metadata
|
24 |
+
|
25 |
+
# other values from the config, defined by the needs of env.py,
|
26 |
+
# can be acquired:
|
27 |
+
# my_important_option = config.get_main_option("my_important_option")
|
28 |
+
# ... etc.
|
29 |
+
|
30 |
+
config.set_section_option("alembic", "sqlalchemy.url", settings.DATABASE_URL)
|
31 |
+
|
32 |
+
|
33 |
+
def run_migrations_offline():
|
34 |
+
"""Run migrations in 'offline' mode.
|
35 |
+
|
36 |
+
This configures the context with just a URL
|
37 |
+
and not an Engine, though an Engine is acceptable
|
38 |
+
here as well. By skipping the Engine creation
|
39 |
+
we don't even need a DBAPI to be available.
|
40 |
+
|
41 |
+
Calls to context.execute() here emit the given string to the
|
42 |
+
script output.
|
43 |
+
|
44 |
+
"""
|
45 |
+
url = config.get_main_option("sqlalchemy.url")
|
46 |
+
context.configure(
|
47 |
+
url=url,
|
48 |
+
target_metadata=target_metadata,
|
49 |
+
literal_binds=True,
|
50 |
+
dialect_opts={"paramstyle": "named"},
|
51 |
+
)
|
52 |
+
|
53 |
+
with context.begin_transaction():
|
54 |
+
context.run_migrations()
|
55 |
+
|
56 |
+
|
57 |
+
def run_migrations_online():
|
58 |
+
"""Run migrations in 'online' mode.
|
59 |
+
|
60 |
+
In this scenario we need to create an Engine
|
61 |
+
and associate a connection with the context.
|
62 |
+
|
63 |
+
"""
|
64 |
+
connectable = engine_from_config(
|
65 |
+
config.get_section(config.config_ini_section),
|
66 |
+
prefix="sqlalchemy.",
|
67 |
+
poolclass=pool.NullPool,
|
68 |
+
)
|
69 |
+
|
70 |
+
with connectable.connect() as connection:
|
71 |
+
context.configure(connection=connection, target_metadata=target_metadata)
|
72 |
+
|
73 |
+
with context.begin_transaction():
|
74 |
+
context.run_migrations()
|
75 |
+
|
76 |
+
|
77 |
+
if context.is_offline_mode():
|
78 |
+
run_migrations_offline()
|
79 |
+
else:
|
80 |
+
run_migrations_online()
|
alembic/script.py.mako
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""${message}
|
2 |
+
|
3 |
+
Revision ID: ${up_revision}
|
4 |
+
Revises: ${down_revision | comma,n}
|
5 |
+
Create Date: ${create_date}
|
6 |
+
|
7 |
+
"""
|
8 |
+
from alembic import op
|
9 |
+
import sqlalchemy as sa
|
10 |
+
import fastapi_users_db_sqlalchemy
|
11 |
+
${imports if imports else ""}
|
12 |
+
|
13 |
+
# revision identifiers, used by Alembic.
|
14 |
+
revision = ${repr(up_revision)}
|
15 |
+
down_revision = ${repr(down_revision)}
|
16 |
+
branch_labels = ${repr(branch_labels)}
|
17 |
+
depends_on = ${repr(depends_on)}
|
18 |
+
|
19 |
+
|
20 |
+
def upgrade():
|
21 |
+
${upgrades if upgrades else "pass"}
|
22 |
+
|
23 |
+
|
24 |
+
def downgrade():
|
25 |
+
${downgrades if downgrades else "pass"}
|
alembic/versions/__pycache__/5c89a726934c_add_item_migration.cpython-310.pyc
ADDED
Binary file (1.53 kB). View file
|
|
alembic/versions/__pycache__/7e09fa75df7a_add_initial_migration.cpython-310.pyc
ADDED
Binary file (1.3 kB). View file
|
|
alembic/versions/__pycache__/98ffdabb3a7a_adding_docs.cpython-310.pyc
ADDED
Binary file (666 Bytes). View file
|
|
alembic/versions/__pycache__/ae0b63479948_init_db.cpython-310.pyc
ADDED
Binary file (1.46 kB). View file
|
|
alembic/versions/__pycache__/afef0f70285c_adding_oauth_support.cpython-310.pyc
ADDED
Binary file (2.63 kB). View file
|
|
alembic/versions/__pycache__/d8d827ad33ee_init_db.cpython-310.pyc
ADDED
Binary file (1.99 kB). View file
|
|
alembic/versions/__pycache__/db6822e47425_init_db.cpython-310.pyc
ADDED
Binary file (1.24 kB). View file
|
|
alembic/versions/d8d827ad33ee_init_db.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""init db
|
2 |
+
|
3 |
+
Revision ID: d8d827ad33ee
|
4 |
+
Revises:
|
5 |
+
Create Date: 2023-07-14 04:15:22.710735
|
6 |
+
|
7 |
+
"""
|
8 |
+
from alembic import op
|
9 |
+
import sqlalchemy as sa
|
10 |
+
import fastapi_users_db_sqlalchemy
|
11 |
+
|
12 |
+
|
13 |
+
# revision identifiers, used by Alembic.
|
14 |
+
revision = 'd8d827ad33ee'
|
15 |
+
down_revision = None
|
16 |
+
branch_labels = None
|
17 |
+
depends_on = None
|
18 |
+
|
19 |
+
|
20 |
+
def upgrade():
|
21 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
22 |
+
op.create_table('user',
|
23 |
+
sa.Column('created', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
24 |
+
sa.Column('id', fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
|
25 |
+
sa.Column('email', sa.String(length=320), nullable=False),
|
26 |
+
sa.Column('hashed_password', sa.String(length=1024), nullable=False),
|
27 |
+
sa.Column('is_active', sa.Boolean(), nullable=False),
|
28 |
+
sa.Column('is_superuser', sa.Boolean(), nullable=False),
|
29 |
+
sa.Column('is_verified', sa.Boolean(), nullable=False),
|
30 |
+
sa.PrimaryKeyConstraint('id')
|
31 |
+
)
|
32 |
+
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
|
33 |
+
op.create_table('document',
|
34 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
35 |
+
sa.Column('user_id', fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
|
36 |
+
sa.Column('name', sa.String(), nullable=False),
|
37 |
+
sa.Column('created', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
38 |
+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
39 |
+
sa.PrimaryKeyConstraint('id')
|
40 |
+
)
|
41 |
+
op.create_table('oauth_account',
|
42 |
+
sa.Column('id', fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
|
43 |
+
sa.Column('user_id', fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
|
44 |
+
sa.Column('oauth_name', sa.String(length=100), nullable=False),
|
45 |
+
sa.Column('access_token', sa.String(length=1024), nullable=False),
|
46 |
+
sa.Column('expires_at', sa.Integer(), nullable=True),
|
47 |
+
sa.Column('refresh_token', sa.String(length=1024), nullable=True),
|
48 |
+
sa.Column('account_id', sa.String(length=320), nullable=False),
|
49 |
+
sa.Column('account_email', sa.String(length=320), nullable=False),
|
50 |
+
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='cascade'),
|
51 |
+
sa.PrimaryKeyConstraint('id')
|
52 |
+
)
|
53 |
+
op.create_index(op.f('ix_oauth_account_account_id'), 'oauth_account', ['account_id'], unique=False)
|
54 |
+
op.create_index(op.f('ix_oauth_account_oauth_name'), 'oauth_account', ['oauth_name'], unique=False)
|
55 |
+
# ### end Alembic commands ###
|
56 |
+
|
57 |
+
|
58 |
+
def downgrade():
|
59 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
60 |
+
op.drop_index(op.f('ix_oauth_account_oauth_name'), table_name='oauth_account')
|
61 |
+
op.drop_index(op.f('ix_oauth_account_account_id'), table_name='oauth_account')
|
62 |
+
op.drop_table('oauth_account')
|
63 |
+
op.drop_table('document')
|
64 |
+
op.drop_index(op.f('ix_user_email'), table_name='user')
|
65 |
+
op.drop_table('user')
|
66 |
+
# ### end Alembic commands ###
|
app/__pycache__/db.cpython-310.pyc
ADDED
Binary file (734 Bytes). View file
|
|
app/__pycache__/factory.cpython-310.pyc
ADDED
Binary file (3.54 kB). View file
|
|
app/api/__init__.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
|
3 |
+
from app.api import users, utils, queries, documents
|
4 |
+
|
5 |
+
api_router = APIRouter()
|
6 |
+
|
7 |
+
api_router.include_router(utils.router, tags=["utils"])
|
8 |
+
api_router.include_router(users.router, tags=["users"])
|
9 |
+
api_router.include_router(queries.router, tags=["queries"])
|
10 |
+
api_router.include_router(documents.router, tags=["documents"])
|
app/api/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (401 Bytes). View file
|
|
app/api/__pycache__/documents.cpython-310.pyc
ADDED
Binary file (2.58 kB). View file
|
|
app/api/__pycache__/queries.cpython-310.pyc
ADDED
Binary file (1.81 kB). View file
|
|
app/api/__pycache__/users.cpython-310.pyc
ADDED
Binary file (1.19 kB). View file
|
|
app/api/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (501 Bytes). View file
|
|
app/api/documents.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
3 |
+
from starlette.responses import Response
|
4 |
+
from app.schemas.document import UpsertResponse
|
5 |
+
from app.parser.parser import get_document_from_file
|
6 |
+
from app.parser.chunk import chunk_text
|
7 |
+
import requests
|
8 |
+
|
9 |
+
from app.deps.users import current_user
|
10 |
+
from app.models.user import User
|
11 |
+
from app.parser.parser import get_document_from_file
|
12 |
+
from sqlalchemy.ext.asyncio.session import AsyncSession
|
13 |
+
from app.deps.db import get_async_session
|
14 |
+
|
15 |
+
from app.models.document import Document
|
16 |
+
from uuid import uuid4
|
17 |
+
|
18 |
+
from app.vectorstore.qdrant import QdrantManager
|
19 |
+
from app.openai.base import OpenAiManager
|
20 |
+
from sqlalchemy.future import select
|
21 |
+
|
22 |
+
|
23 |
+
router = APIRouter(prefix="/documents")
|
24 |
+
|
25 |
+
|
26 |
+
@router.post(
|
27 |
+
"/upsert-file",
|
28 |
+
response_model=UpsertResponse,
|
29 |
+
)
|
30 |
+
async def upsert_file(
|
31 |
+
file: UploadFile = File(...),
|
32 |
+
session: AsyncSession = Depends(get_async_session),
|
33 |
+
user: User = Depends(current_user),
|
34 |
+
):
|
35 |
+
document = await get_document_from_file(file)
|
36 |
+
chunks = chunk_text(document.text, max_size=2000)
|
37 |
+
db_document = Document(user_id=str(user.id).replace("-", ""), name=file.filename)
|
38 |
+
session.add(db_document)
|
39 |
+
|
40 |
+
await session.commit()
|
41 |
+
|
42 |
+
openai_manager = OpenAiManager()
|
43 |
+
ids = [uuid4().hex for chunk in chunks]
|
44 |
+
payloads = [
|
45 |
+
{
|
46 |
+
"user_id": str(user.id).replace("-", ""),
|
47 |
+
"document_id": db_document.id,
|
48 |
+
"chunk": chunk,
|
49 |
+
}
|
50 |
+
for chunk in chunks
|
51 |
+
]
|
52 |
+
embeddings = openai_manager.get_embeddings(chunks)
|
53 |
+
|
54 |
+
vector_manager = QdrantManager()
|
55 |
+
response = vector_manager.upsert_points(ids, payloads, embeddings)
|
56 |
+
|
57 |
+
return UpsertResponse(id=db_document.id)
|
58 |
+
|
59 |
+
|
60 |
+
@router.get("/")
|
61 |
+
async def get_documents(
|
62 |
+
session: AsyncSession = Depends(get_async_session),
|
63 |
+
user: User = Depends(current_user),
|
64 |
+
):
|
65 |
+
statement = select(Document).where(
|
66 |
+
Document.user_id == str(user.id).replace("-", "")
|
67 |
+
)
|
68 |
+
db_documents = await session.execute(statement)
|
69 |
+
|
70 |
+
documents = [
|
71 |
+
{"id": document.id, "name": document.name}
|
72 |
+
for document in db_documents.scalars().all()
|
73 |
+
]
|
74 |
+
|
75 |
+
return {"documents": documents}
|
app/api/queries.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
3 |
+
from starlette.responses import Response
|
4 |
+
import requests
|
5 |
+
|
6 |
+
|
7 |
+
from app.deps.users import current_user
|
8 |
+
from app.models.user import User
|
9 |
+
|
10 |
+
from app.vectorstore.qdrant import qdrant_manager
|
11 |
+
from app.openai.base import openai_manager
|
12 |
+
from app.openai.core import ask, filter, summarize
|
13 |
+
|
14 |
+
from pydantic import BaseModel
|
15 |
+
|
16 |
+
|
17 |
+
class QueryRequest(BaseModel):
|
18 |
+
query: str
|
19 |
+
document_id: int
|
20 |
+
|
21 |
+
|
22 |
+
class QueryResponse(BaseModel):
|
23 |
+
answer: str
|
24 |
+
document_id: int
|
25 |
+
|
26 |
+
|
27 |
+
router = APIRouter(prefix="/queries")
|
28 |
+
|
29 |
+
|
30 |
+
@router.post("/")
|
31 |
+
async def query(
|
32 |
+
query_request: QueryRequest,
|
33 |
+
response: Response,
|
34 |
+
user: User = Depends(current_user),
|
35 |
+
) -> QueryResponse:
|
36 |
+
# add check that this user actually owns this document
|
37 |
+
|
38 |
+
query_vector = openai_manager.get_embedding(query_request.query)
|
39 |
+
|
40 |
+
# print(">>>>>>>>>>>>")
|
41 |
+
# print("query vector")
|
42 |
+
# print(query_vector)
|
43 |
+
|
44 |
+
# print(">>>>>>>>>>>>>>>")
|
45 |
+
# print("document_id: ", query_request.document_id)
|
46 |
+
# print("--------------")
|
47 |
+
|
48 |
+
# print(">>>>>>>>>>>>")
|
49 |
+
# print("user_id")
|
50 |
+
# print(user.id.hex)
|
51 |
+
|
52 |
+
points = qdrant_manager.search_point(
|
53 |
+
query_vector=query_vector,
|
54 |
+
user_id=str(user.id.hex),
|
55 |
+
document_id=int(query_request.document_id),
|
56 |
+
limit=1000,
|
57 |
+
)
|
58 |
+
|
59 |
+
# print(">>>>>>>>>>>>")
|
60 |
+
# print("points")
|
61 |
+
# print(points)
|
62 |
+
|
63 |
+
context = "\n\n\n".join([point.payload["chunk"] for point in points])
|
64 |
+
# print(">>>>>>>>>>>>")
|
65 |
+
# print("context")
|
66 |
+
# print(context)
|
67 |
+
|
68 |
+
# filter_response = filter(context, query_request.query, openai_manager)
|
69 |
+
# print(">>>>>>>>>>>>>>>>")
|
70 |
+
# print("filter resopnse")
|
71 |
+
# print(filter_response)
|
72 |
+
# print("----------------")
|
73 |
+
# remove later
|
74 |
+
filter_response = True
|
75 |
+
if filter_response:
|
76 |
+
answer = ask(
|
77 |
+
context,
|
78 |
+
query_request.query,
|
79 |
+
openai_manager,
|
80 |
+
)
|
81 |
+
|
82 |
+
query_response = QueryResponse(
|
83 |
+
answer=answer, document_id=query_request.document_id
|
84 |
+
)
|
85 |
+
|
86 |
+
else:
|
87 |
+
query_response = QueryResponse(
|
88 |
+
answer="Sorry, Your question is out of Context!",
|
89 |
+
document_id=query_request.document_id,
|
90 |
+
)
|
91 |
+
|
92 |
+
return query_response
|
app/api/users.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any, List
|
2 |
+
|
3 |
+
from fastapi.params import Depends
|
4 |
+
from fastapi.routing import APIRouter
|
5 |
+
from sqlalchemy import func, select
|
6 |
+
from sqlalchemy.ext.asyncio.session import AsyncSession
|
7 |
+
from starlette.responses import Response
|
8 |
+
|
9 |
+
from app.deps.db import get_async_session
|
10 |
+
from app.deps.users import current_superuser
|
11 |
+
from app.models.user import User
|
12 |
+
from app.schemas.user import UserRead
|
13 |
+
|
14 |
+
router = APIRouter()
|
15 |
+
|
16 |
+
|
17 |
+
@router.get("/users", response_model=List[UserRead])
|
18 |
+
async def get_users(
|
19 |
+
response: Response,
|
20 |
+
session: AsyncSession = Depends(get_async_session),
|
21 |
+
user: User = Depends(current_superuser),
|
22 |
+
skip: int = 0,
|
23 |
+
limit: int = 100,
|
24 |
+
) -> Any:
|
25 |
+
total = await session.scalar(select(func.count(User.id)))
|
26 |
+
users = (
|
27 |
+
(await session.execute(select(User).offset(skip).limit(limit))).scalars().all()
|
28 |
+
)
|
29 |
+
response.headers["Content-Range"] = f"{skip}-{skip + len(users)}/{total}"
|
30 |
+
return users
|
app/api/utils.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
+
from fastapi import APIRouter
|
4 |
+
|
5 |
+
from app.schemas.msg import Msg
|
6 |
+
|
7 |
+
router = APIRouter()
|
8 |
+
|
9 |
+
|
10 |
+
@router.get(
|
11 |
+
"/hello-world",
|
12 |
+
response_model=Msg,
|
13 |
+
status_code=200,
|
14 |
+
include_in_schema=False,
|
15 |
+
)
|
16 |
+
def test_hello_world() -> Any:
|
17 |
+
return {"msg": "Hello world!"}
|
app/core/__pycache__/config.cpython-310.pyc
ADDED
Binary file (1.8 kB). View file
|
|
app/core/__pycache__/logger.cpython-310.pyc
ADDED
Binary file (220 Bytes). View file
|
|
app/core/config.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
from typing import Any, Dict, List, Optional
|
3 |
+
|
4 |
+
from pydantic import BaseSettings, HttpUrl, PostgresDsn, validator
|
5 |
+
from pydantic.networks import AnyHttpUrl
|
6 |
+
|
7 |
+
|
8 |
+
class Settings(BaseSettings):
|
9 |
+
PROJECT_NAME: str = "QueryX"
|
10 |
+
|
11 |
+
SENTRY_DSN: Optional[HttpUrl] = None
|
12 |
+
|
13 |
+
API_PATH: str = "/api/v1"
|
14 |
+
|
15 |
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = 7 * 24 * 60 # 7 days
|
16 |
+
|
17 |
+
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [
|
18 |
+
"http://0.0.0.0:9000",
|
19 |
+
"http://0.0.0.0:8000",
|
20 |
+
]
|
21 |
+
|
22 |
+
# The following variables need to be defined in environment
|
23 |
+
|
24 |
+
TEST_DATABASE_URL: Optional[PostgresDsn]
|
25 |
+
DATABASE_URL: PostgresDsn
|
26 |
+
ASYNC_DATABASE_URL: Optional[PostgresDsn]
|
27 |
+
|
28 |
+
@validator("DATABASE_URL", pre=True)
|
29 |
+
def build_test_database_url(cls, v: Optional[str], values: Dict[str, Any]):
|
30 |
+
"""Overrides DATABASE_URL with TEST_DATABASE_URL in test environment."""
|
31 |
+
url = v
|
32 |
+
if "pytest" in sys.modules:
|
33 |
+
if not values.get("TEST_DATABASE_URL"):
|
34 |
+
raise Exception(
|
35 |
+
"pytest detected, but TEST_DATABASE_URL is not set in environment"
|
36 |
+
)
|
37 |
+
url = values["TEST_DATABASE_URL"]
|
38 |
+
if url:
|
39 |
+
return url.replace("postgres://", "postgresql://")
|
40 |
+
return url
|
41 |
+
|
42 |
+
@validator("ASYNC_DATABASE_URL")
|
43 |
+
def build_async_database_url(cls, v: Optional[str], values: Dict[str, Any]):
|
44 |
+
"""Builds ASYNC_DATABASE_URL from DATABASE_URL."""
|
45 |
+
v = values["DATABASE_URL"]
|
46 |
+
return v.replace("postgresql", "postgresql+asyncpg", 1) if v else v
|
47 |
+
|
48 |
+
SECRET_KEY: str
|
49 |
+
# END: required environment variables
|
50 |
+
|
51 |
+
|
52 |
+
settings = Settings()
|
app/core/logger.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
|
3 |
+
logger = logging.getLogger(__name__)
|
4 |
+
logging.basicConfig(level=logging.INFO)
|
app/db.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
4 |
+
from sqlalchemy.orm import DeclarativeBase
|
5 |
+
|
6 |
+
from app.core.config import settings
|
7 |
+
|
8 |
+
async_engine = create_async_engine(str(settings.ASYNC_DATABASE_URL), pool_pre_ping=True)
|
9 |
+
|
10 |
+
async_session_maker = async_sessionmaker(
|
11 |
+
async_engine,
|
12 |
+
class_=AsyncSession,
|
13 |
+
expire_on_commit=False,
|
14 |
+
autocommit=False,
|
15 |
+
autoflush=False,
|
16 |
+
)
|
17 |
+
|
18 |
+
|
19 |
+
class Base(DeclarativeBase):
|
20 |
+
id: Any
|
app/deps/__pycache__/db.cpython-310.pyc
ADDED
Binary file (513 Bytes). View file
|
|
app/deps/__pycache__/users.cpython-310.pyc
ADDED
Binary file (1.78 kB). View file
|
|
app/deps/db.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import AsyncGenerator
|
2 |
+
|
3 |
+
from sqlalchemy.ext.asyncio.session import AsyncSession
|
4 |
+
|
5 |
+
from app.db import async_session_maker
|
6 |
+
|
7 |
+
|
8 |
+
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
9 |
+
async with async_session_maker() as session:
|
10 |
+
yield session
|
11 |
+
await session.close()
|
app/deps/qdrant.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import qdrant_client
|
2 |
+
|
3 |
+
|
4 |
+
def get_qdrant_client():
|
5 |
+
return qdrant_client.QdrantClient(host=host, port=port, api_key=qdrant_api_key)
|
app/deps/request_params.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from typing import Callable, Optional, Type
|
3 |
+
|
4 |
+
from fastapi import HTTPException, Query
|
5 |
+
from sqlalchemy import UnaryExpression, asc, desc
|
6 |
+
|
7 |
+
from app.db import Base
|
8 |
+
from app.schemas.request_params import RequestParams
|
9 |
+
|
10 |
+
|
11 |
+
def parse_react_admin_params(model: Type[Base]) -> Callable:
|
12 |
+
"""Parses sort and range parameters coming from a react-admin request"""
|
13 |
+
|
14 |
+
def inner(
|
15 |
+
sort_: Optional[str] = Query(
|
16 |
+
None,
|
17 |
+
alias="sort",
|
18 |
+
description='Format: `["field_name", "direction"]`',
|
19 |
+
example='["id", "ASC"]',
|
20 |
+
),
|
21 |
+
range_: Optional[str] = Query(
|
22 |
+
None,
|
23 |
+
alias="range",
|
24 |
+
description="Format: `[start, end]`",
|
25 |
+
example="[0, 10]",
|
26 |
+
),
|
27 |
+
) -> RequestParams:
|
28 |
+
skip, limit = 0, 10
|
29 |
+
if range_:
|
30 |
+
start, end = json.loads(range_)
|
31 |
+
skip, limit = start, (end - start + 1)
|
32 |
+
|
33 |
+
order_by: UnaryExpression = desc(model.id)
|
34 |
+
if sort_:
|
35 |
+
sort_column, sort_order = json.loads(sort_)
|
36 |
+
if sort_order.lower() == "asc":
|
37 |
+
direction = asc
|
38 |
+
elif sort_order.lower() == "desc":
|
39 |
+
direction = desc
|
40 |
+
else:
|
41 |
+
raise HTTPException(400, f"Invalid sort direction {sort_order}")
|
42 |
+
order_by = direction(model.__table__.c[sort_column])
|
43 |
+
|
44 |
+
return RequestParams(skip=skip, limit=limit, order_by=order_by)
|
45 |
+
|
46 |
+
return inner
|
app/deps/users.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uuid
|
2 |
+
|
3 |
+
from fastapi import Depends
|
4 |
+
from fastapi_users import FastAPIUsers
|
5 |
+
from fastapi_users.authentication import (
|
6 |
+
AuthenticationBackend,
|
7 |
+
BearerTransport,
|
8 |
+
JWTStrategy,
|
9 |
+
)
|
10 |
+
from fastapi_users.manager import BaseUserManager, UUIDIDMixin
|
11 |
+
from fastapi_users_db_sqlalchemy import SQLAlchemyUserDatabase
|
12 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
13 |
+
|
14 |
+
from app.core.config import settings
|
15 |
+
from app.deps.db import get_async_session
|
16 |
+
from app.models.user import OAuthAccount, User as UserModel
|
17 |
+
|
18 |
+
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
19 |
+
|
20 |
+
|
21 |
+
def get_jwt_strategy() -> JWTStrategy:
|
22 |
+
return JWTStrategy(
|
23 |
+
secret=settings.SECRET_KEY,
|
24 |
+
lifetime_seconds=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
jwt_authentication = AuthenticationBackend(
|
29 |
+
name="jwt",
|
30 |
+
transport=bearer_transport,
|
31 |
+
get_strategy=get_jwt_strategy,
|
32 |
+
)
|
33 |
+
|
34 |
+
|
35 |
+
class UserManager(UUIDIDMixin, BaseUserManager[UserModel, uuid.UUID]):
|
36 |
+
reset_password_token_secret = settings.SECRET_KEY
|
37 |
+
verification_token_secret = settings.SECRET_KEY
|
38 |
+
|
39 |
+
|
40 |
+
def get_user_db(session: AsyncSession = Depends(get_async_session)):
|
41 |
+
yield SQLAlchemyUserDatabase(session, UserModel, OAuthAccount)
|
42 |
+
|
43 |
+
|
44 |
+
def get_user_manager(user_db=Depends(get_user_db)):
|
45 |
+
yield UserManager(user_db)
|
46 |
+
|
47 |
+
|
48 |
+
fastapi_users = FastAPIUsers(get_user_manager, [jwt_authentication])
|
49 |
+
|
50 |
+
current_user = fastapi_users.current_user(active=True)
|
51 |
+
current_superuser = fastapi_users.current_user(active=True, superuser=True)
|
app/email/send_mail.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
def send_email():
|
2 |
+
raise NotImplementedError
|
app/factory.py
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.routing import APIRoute
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
from fastapi_users import FastAPIUsers
|
5 |
+
from starlette.middleware.cors import CORSMiddleware
|
6 |
+
from starlette.requests import Request
|
7 |
+
from starlette.responses import FileResponse
|
8 |
+
|
9 |
+
from app.api import api_router
|
10 |
+
from app.core.config import settings
|
11 |
+
from app.deps.users import fastapi_users, jwt_authentication
|
12 |
+
from app.schemas.user import UserCreate, UserRead, UserUpdate
|
13 |
+
import os
|
14 |
+
|
15 |
+
|
16 |
+
from httpx_oauth.clients.google import GoogleOAuth2 # google client
|
17 |
+
|
18 |
+
# for dev only
|
19 |
+
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
|
20 |
+
|
21 |
+
|
22 |
+
def create_app():
|
23 |
+
description = f"{settings.PROJECT_NAME} API"
|
24 |
+
app = FastAPI(
|
25 |
+
title=settings.PROJECT_NAME,
|
26 |
+
openapi_url=f"{settings.API_PATH}/openapi.json",
|
27 |
+
docs_url="/docs/",
|
28 |
+
description=description,
|
29 |
+
redoc_url=None,
|
30 |
+
)
|
31 |
+
setup_routers(app, fastapi_users)
|
32 |
+
setup_cors_middleware(app)
|
33 |
+
serve_static_app(app)
|
34 |
+
return app
|
35 |
+
|
36 |
+
|
37 |
+
def setup_routers(app: FastAPI, fastapi_users: FastAPIUsers) -> None:
|
38 |
+
app.include_router(api_router, prefix=settings.API_PATH)
|
39 |
+
app.include_router(
|
40 |
+
fastapi_users.get_auth_router(
|
41 |
+
jwt_authentication,
|
42 |
+
requires_verification=False,
|
43 |
+
),
|
44 |
+
prefix=f"{settings.API_PATH}/auth/jwt",
|
45 |
+
tags=["auth"],
|
46 |
+
)
|
47 |
+
app.include_router(
|
48 |
+
fastapi_users.get_register_router(UserRead, UserCreate),
|
49 |
+
prefix=f"{settings.API_PATH}/auth",
|
50 |
+
tags=["auth"],
|
51 |
+
)
|
52 |
+
app.include_router(
|
53 |
+
fastapi_users.get_users_router(
|
54 |
+
UserRead, UserUpdate, requires_verification=False
|
55 |
+
),
|
56 |
+
prefix=f"{settings.API_PATH}/users",
|
57 |
+
tags=["users"],
|
58 |
+
)
|
59 |
+
|
60 |
+
google_oauth_client = GoogleOAuth2(
|
61 |
+
os.getenv("GOOGLE_OAUTH_CLIENT_ID", ""),
|
62 |
+
os.getenv("GOOGLE_OAUTH_CLIENT_SECRET", ""),
|
63 |
+
)
|
64 |
+
|
65 |
+
app.include_router(
|
66 |
+
fastapi_users.get_oauth_router(
|
67 |
+
google_oauth_client, jwt_authentication, "SECRET"
|
68 |
+
),
|
69 |
+
prefix="/auth/google",
|
70 |
+
tags=["auth"],
|
71 |
+
)
|
72 |
+
# The following operation needs to be at the end of this function
|
73 |
+
use_route_names_as_operation_ids(app)
|
74 |
+
|
75 |
+
|
76 |
+
def serve_static_app(app):
|
77 |
+
app.mount("/", StaticFiles(directory="static"), name="static")
|
78 |
+
|
79 |
+
@app.middleware("http")
|
80 |
+
async def _add_404_middleware(request: Request, call_next):
|
81 |
+
"""Serves static assets on 404"""
|
82 |
+
response = await call_next(request)
|
83 |
+
path = request["path"]
|
84 |
+
if path.startswith(settings.API_PATH) or path.startswith("/docs"):
|
85 |
+
return response
|
86 |
+
if response.status_code == 404:
|
87 |
+
return FileResponse("static/index.html")
|
88 |
+
return response
|
89 |
+
|
90 |
+
|
91 |
+
def setup_cors_middleware(app):
|
92 |
+
if settings.BACKEND_CORS_ORIGINS:
|
93 |
+
app.add_middleware(
|
94 |
+
CORSMiddleware,
|
95 |
+
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
|
96 |
+
allow_credentials=True,
|
97 |
+
allow_methods=["*"],
|
98 |
+
expose_headers=["Content-Range", "Range"],
|
99 |
+
allow_headers=["Authorization", "Range", "Content-Range"],
|
100 |
+
)
|
101 |
+
|
102 |
+
|
103 |
+
def use_route_names_as_operation_ids(app: FastAPI) -> None:
|
104 |
+
"""
|
105 |
+
Simplify operation IDs so that generated API clients have simpler function
|
106 |
+
names.
|
107 |
+
|
108 |
+
Should be called only after all routes have been added.
|
109 |
+
"""
|
110 |
+
route_names = set()
|
111 |
+
for route in app.routes:
|
112 |
+
if isinstance(route, APIRoute):
|
113 |
+
if route.name in route_names:
|
114 |
+
raise Exception("Route function names should be unique")
|
115 |
+
route.operation_id = route.name
|
116 |
+
route_names.add(route.name)
|
app/models/__init__.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Import all models here so alembic can discover them
|
2 |
+
from app.models.user import User
|
3 |
+
from app.models.document import Document
|
app/models/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (216 Bytes). View file
|
|
app/models/__pycache__/document.cpython-310.pyc
ADDED
Binary file (1.17 kB). View file
|
|
app/models/__pycache__/user.cpython-310.pyc
ADDED
Binary file (1.42 kB). View file
|
|
app/models/document.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from typing import TYPE_CHECKING
|
3 |
+
from uuid import UUID
|
4 |
+
|
5 |
+
from fastapi_users_db_sqlalchemy import GUID
|
6 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
7 |
+
from sqlalchemy.sql.functions import func
|
8 |
+
from sqlalchemy.sql.schema import ForeignKey
|
9 |
+
from sqlalchemy.sql.sqltypes import DateTime
|
10 |
+
|
11 |
+
from app.db import Base
|
12 |
+
|
13 |
+
if TYPE_CHECKING:
|
14 |
+
from app.models.user import User
|
15 |
+
|
16 |
+
|
17 |
+
class Document(Base):
|
18 |
+
__tablename__ = "document"
|
19 |
+
|
20 |
+
id: Mapped[int] = mapped_column(primary_key=True)
|
21 |
+
user_id: Mapped[UUID] = mapped_column(GUID, ForeignKey("user.id"))
|
22 |
+
user: Mapped["User"] = relationship(back_populates="documents")
|
23 |
+
name: Mapped[str]
|
24 |
+
|
25 |
+
created: Mapped[datetime] = mapped_column(
|
26 |
+
DateTime(timezone=True), server_default=func.now()
|
27 |
+
)
|
app/models/user.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from typing import TYPE_CHECKING, List
|
3 |
+
|
4 |
+
|
5 |
+
from fastapi_users_db_sqlalchemy import (
|
6 |
+
SQLAlchemyBaseUserTableUUID,
|
7 |
+
SQLAlchemyBaseOAuthAccountTableUUID,
|
8 |
+
)
|
9 |
+
|
10 |
+
from sqlalchemy import DateTime
|
11 |
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
12 |
+
from sqlalchemy.sql.functions import func
|
13 |
+
|
14 |
+
from app.db import Base
|
15 |
+
|
16 |
+
|
17 |
+
if TYPE_CHECKING:
|
18 |
+
from app.models.document import Document
|
19 |
+
|
20 |
+
|
21 |
+
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
|
22 |
+
pass
|
23 |
+
|
24 |
+
|
25 |
+
class User(SQLAlchemyBaseUserTableUUID, Base):
|
26 |
+
__tablename__ = "user"
|
27 |
+
|
28 |
+
oauth_accounts: Mapped[List[OAuthAccount]] = relationship(
|
29 |
+
"OAuthAccount", lazy="joined"
|
30 |
+
)
|
31 |
+
|
32 |
+
created: Mapped[datetime] = mapped_column(
|
33 |
+
DateTime(timezone=True), server_default=func.now()
|
34 |
+
)
|
35 |
+
|
36 |
+
documents: Mapped["Document"] = relationship(
|
37 |
+
back_populates="user", cascade="all, delete"
|
38 |
+
)
|
39 |
+
|
40 |
+
def __repr__(self):
|
41 |
+
return f"User(id={self.id!r}, name={self.email!r})"
|
42 |
+
|
43 |
+
|
44 |
+
# add activated column
|
app/openai/__pycache__/base.cpython-310.pyc
ADDED
Binary file (2.46 kB). View file
|
|
app/openai/__pycache__/core.cpython-310.pyc
ADDED
Binary file (1.23 kB). View file
|
|
app/openai/base.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
# from dotenv import load_dotenv
|
4 |
+
import openai
|
5 |
+
|
6 |
+
|
7 |
+
class OpenAiManager:
|
8 |
+
def __init__(self):
|
9 |
+
# load_dotenv()
|
10 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
11 |
+
|
12 |
+
def get_completion(
|
13 |
+
self,
|
14 |
+
prompt,
|
15 |
+
model="text-davinci-003",
|
16 |
+
max_tokens=128,
|
17 |
+
temperature=0,
|
18 |
+
):
|
19 |
+
response = None
|
20 |
+
try:
|
21 |
+
response = openai.Completion.create(
|
22 |
+
prompt=prompt,
|
23 |
+
max_tokens=max_tokens,
|
24 |
+
model=model,
|
25 |
+
temperature=temperature,
|
26 |
+
)["choices"][0]["text"]
|
27 |
+
|
28 |
+
except Exception as err:
|
29 |
+
print(f"Sorry, There was a problem \n\n {err}")
|
30 |
+
|
31 |
+
return response
|
32 |
+
|
33 |
+
def get_chat_completion(self, prompt, model="gpt-3.5-turbo"):
|
34 |
+
response = None
|
35 |
+
try:
|
36 |
+
response = (
|
37 |
+
openai.ChatCompletion.create(
|
38 |
+
model=model,
|
39 |
+
messages=[
|
40 |
+
{
|
41 |
+
"role": "system",
|
42 |
+
"content": prompt,
|
43 |
+
}
|
44 |
+
],
|
45 |
+
)
|
46 |
+
.choices[0]
|
47 |
+
.message.content.strip()
|
48 |
+
)
|
49 |
+
|
50 |
+
except Exception as err:
|
51 |
+
print(f"Sorry, There was a problem \n\n {err}")
|
52 |
+
|
53 |
+
return response
|
54 |
+
|
55 |
+
def get_embedding(self, prompt, model="text-embedding-ada-002"):
|
56 |
+
prompt = prompt.replace("\n", " ")
|
57 |
+
|
58 |
+
embedding = None
|
59 |
+
try:
|
60 |
+
embedding = openai.Embedding.create(input=[prompt], model=model)["data"][0][
|
61 |
+
"embedding"
|
62 |
+
]
|
63 |
+
|
64 |
+
except Exception as err:
|
65 |
+
print(f"Sorry, There was a problem {err}")
|
66 |
+
|
67 |
+
return embedding
|
68 |
+
|
69 |
+
def get_embeddings(self, prompts, model="text-embedding-ada-002"):
|
70 |
+
prompts = [prompt.replace("\n", " ") for prompt in prompts]
|
71 |
+
|
72 |
+
embeddings = None
|
73 |
+
try:
|
74 |
+
embeddings = openai.Embedding.create(input=prompts, model=model)["data"]
|
75 |
+
|
76 |
+
except Exception as err:
|
77 |
+
print(f"Sorry, There was a problem {err}")
|
78 |
+
|
79 |
+
return [embedding["embedding"] for embedding in embeddings]
|
80 |
+
|
81 |
+
|
82 |
+
openai_manager = OpenAiManager()
|