From b6f3ec17a66501c8e91f0369d6cca87eaacbc3c1 Mon Sep 17 00:00:00 2001 From: awtkns <32209255+awtkns@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:23:02 -0700 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9A=80Realtime=20presence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next/src/hooks/useSocket.ts | 73 ++++++- next/src/hooks/useWorkflow.ts | 26 ++- next/src/pages/workflow/[workflow].tsx | 13 +- next/src/utils/user.ts | 8 +- platform/poetry.lock | 198 +++--------------- platform/pyproject.toml | 1 + platform/reworkd_platform/schemas/user.py | 1 + platform/reworkd_platform/services/sockets.py | 18 ++ .../reworkd_platform/web/api/auth/views.py | 16 +- 9 files changed, 159 insertions(+), 195 deletions(-) diff --git a/next/src/hooks/useSocket.ts b/next/src/hooks/useSocket.ts index 836ec9bc57..59ef410912 100644 --- a/next/src/hooks/useSocket.ts +++ b/next/src/hooks/useSocket.ts @@ -1,33 +1,86 @@ /* eslint-disable react-hooks/exhaustive-deps */ import Pusher from "pusher-js"; -import { useEffect } from "react"; -import type { z } from "zod"; +import { useEffect, useState } from "react"; +import { z } from "zod"; import { env } from "../env/client.mjs"; +const PresenceInfoSchema = z.object({ + name: z.string().nullish(), + email: z.string().nullish(), + image: z.string().nullish(), +}); + +const PresenceSubscriptionSucceededSchema = z.object({ + count: z.number(), + me: z.object({ + id: z.string(), + info: PresenceInfoSchema, + }), + members: z.record(PresenceInfoSchema), +}); + +const PresenceMemberEventSchema = z.object({ + id: z.string(), + info: PresenceInfoSchema, +}); + +type PresenceInfo = z.infer; + export default function useSocket( channelName: string, + accessToken: string | undefined, eventSchema: T, callback: (data: z.infer) => void ) { + const [members, setMembers] = useState>({}); + useEffect(() => { const app_key = env.NEXT_PUBLIC_PUSHER_APP_KEY; - if (!app_key) return () => void 0; - - const pusher = new Pusher(app_key, { cluster: "mt1" }); - const channel = pusher.subscribe(channelName); + if (!app_key || !accessToken) return () => void 0; - console.log("subscribed to channel", channelName); + const pusher = new Pusher(app_key, { + cluster: "mt1", + channelAuthorization: { + transport: "ajax", + endpoint: `${env.NEXT_PUBLIC_BACKEND_URL}/api/auth/pusher`, + headers: { + Authorization: `Bearer ${accessToken || ""}`, + }, + }, + }); + const channel = pusher.subscribe("presence-" + channelName); channel.bind("my-event", async (data) => { const obj = (await eventSchema.parse(data)) as z.infer; callback(obj); }); + channel.bind("pusher:subscription_succeeded", async (data) => { + const event = await PresenceSubscriptionSucceededSchema.parseAsync(data); + setMembers(event.members); + }); + + channel.bind("pusher:member_added", async (data) => { + const event = await PresenceMemberEventSchema.parseAsync(data); + + setMembers((prev) => ({ + ...prev, + [event.id]: event.info, + })); + }); + + channel.bind("pusher:member_removed", async (data) => { + const event = await PresenceMemberEventSchema.parseAsync(data); + setMembers(({ [event.id]: _, ...rest }) => rest); + }); + return () => { - pusher.unsubscribe(channelName); + pusher.unsubscribe(channel.name); pusher.disconnect(); - console.warn("unsubscribed from channel", channelName); + setMembers({}); }; - }, []); + }, [accessToken]); + + return members; } diff --git a/next/src/hooks/useWorkflow.ts b/next/src/hooks/useWorkflow.ts index c215c91af6..d38c1ee2e9 100644 --- a/next/src/hooks/useWorkflow.ts +++ b/next/src/hooks/useWorkflow.ts @@ -82,17 +82,22 @@ export const useWorkflow = (workflowId: string, session: Session | null) => { else setSelectedNode(selectedNodes[0]); }, [nodes]); - useSocket(workflowId, eventSchema, ({ nodeId, status, remaining }) => { - updateValue(setNodes, "status", status, (n) => n?.id === nodeId); - updateValue(setEdges, "status", status, (e) => e?.target === nodeId); - - if (remaining === 0) { - setTimeout(() => { - updateValue(setNodes, "status", undefined); - updateValue(setEdges, "status", undefined); - }, 1000); + const members = useSocket( + workflowId, + session?.accessToken, + eventSchema, + ({ nodeId, status, remaining }) => { + updateValue(setNodes, "status", status, (n) => n?.id === nodeId); + updateValue(setEdges, "status", status, (e) => e?.target === nodeId); + + if (remaining === 0) { + setTimeout(() => { + updateValue(setNodes, "status", undefined); + updateValue(setEdges, "status", undefined); + }, 1000); + } } - }); + ); const createNode: createNodeType = (block: NodeBlock) => { const ref = nanoid(11); @@ -160,6 +165,7 @@ export const useWorkflow = (workflowId: string, session: Session | null) => { executeWorkflow: onExecute, createNode, updateNode, + members, }; }; diff --git a/next/src/pages/workflow/[workflow].tsx b/next/src/pages/workflow/[workflow].tsx index 7a663d6c4c..d170247506 100644 --- a/next/src/pages/workflow/[workflow].tsx +++ b/next/src/pages/workflow/[workflow].tsx @@ -13,6 +13,7 @@ import { useAuth } from "../../hooks/useAuth"; import { useWorkflow } from "../../hooks/useWorkflow"; import DashboardLayout from "../../layout/dashboard"; import { languages } from "../../utils/languages"; +import { get_avatar } from "../../utils/user"; const WorkflowPage: NextPage = () => { const { session } = useAuth({ protectedRoute: true }); @@ -20,7 +21,7 @@ const WorkflowPage: NextPage = () => { const [file, setFile] = useState(); - const { nodesModel, edgesModel, selectedNode, saveWorkflow, createNode, updateNode } = + const { nodesModel, edgesModel, selectedNode, saveWorkflow, createNode, updateNode, members } = useWorkflow(query.workflow as string, session); return ( @@ -49,6 +50,16 @@ const WorkflowPage: NextPage = () => { className="min-h-screen flex-1" />
+
+ {Object.entries(members).map(([id, user]) => ( + user avatar + ))} +
} diff --git a/next/src/utils/user.ts b/next/src/utils/user.ts index 69d915cf06..cf67cc3d2c 100644 --- a/next/src/utils/user.ts +++ b/next/src/utils/user.ts @@ -1,6 +1,8 @@ -import type { User } from "next-auth"; - -export const get_avatar = (user?: User) => +export const get_avatar = (user?: { + name?: string | null; + email?: string | null; + image?: string | null; +}) => user?.image || "https://avatar.vercel.sh/" + (user?.email || "") + diff --git a/platform/poetry.lock b/platform/poetry.lock index fd7cf191da..89a383ef7c 100644 --- a/platform/poetry.lock +++ b/platform/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.5" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -113,7 +112,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiokafka" version = "0.8.1" description = "Kafka integration with asyncio." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -160,7 +158,6 @@ zstd = ["zstandard"] name = "aiomysql" version = "0.1.1" description = "MySQL driver for asyncio." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -179,7 +176,6 @@ sa = ["sqlalchemy (>=1.0,<1.4)"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -194,7 +190,6 @@ frozenlist = ">=1.1.0" name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -216,7 +211,6 @@ trio = ["trio (<0.22)"] name = "astor" version = "0.8.1" description = "Read/rewrite/write Python ASTs" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ @@ -228,7 +222,6 @@ files = [ name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -240,7 +233,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -259,7 +251,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "authlib" version = "1.2.1" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." -category = "main" optional = false python-versions = "*" files = [ @@ -274,7 +265,6 @@ cryptography = ">=3.2" name = "autoflake" version = "2.2.0" description = "Removes unused imports and unused variables" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -290,7 +280,6 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} name = "autopep8" version = "2.0.2" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -306,7 +295,6 @@ tomli = {version = "*", markers = "python_version < \"3.11\""} name = "bandit" version = "1.7.5" description = "Security oriented static analyser for python code." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -330,7 +318,6 @@ yaml = ["PyYAML"] name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -349,7 +336,6 @@ lxml = ["lxml"] name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -396,7 +382,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "boto3" version = "1.28.8" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -416,7 +401,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.31.8" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -436,7 +420,6 @@ crt = ["awscrt (==0.16.26)"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -448,7 +431,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -525,7 +507,6 @@ pycparser = "*" name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -537,7 +518,6 @@ files = [ name = "charset-normalizer" version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -552,7 +532,6 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.6" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -567,7 +546,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -579,7 +557,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -655,7 +632,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -701,7 +677,6 @@ test-randomorder = ["pytest-randomly"] name = "darglint" version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." -category = "dev" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -713,7 +688,6 @@ files = [ name = "dataclasses-json" version = "0.5.9" description = "Easily serialize dataclasses to and from JSON" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -733,7 +707,6 @@ dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest ( name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -745,7 +718,6 @@ files = [ name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -757,7 +729,6 @@ files = [ name = "dnspython" version = "2.4.0" description = "DNS toolkit" -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -781,7 +752,6 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -793,7 +763,6 @@ files = [ name = "dotmap" version = "1.3.30" description = "ordered, dynamically-expandable dot-access dictionary" -category = "dev" optional = false python-versions = "*" files = [ @@ -805,7 +774,6 @@ files = [ name = "eradicate" version = "2.3.0" description = "Removes commented-out code." -category = "dev" optional = false python-versions = "*" files = [ @@ -817,7 +785,6 @@ files = [ name = "exceptiongroup" version = "1.1.2" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -832,7 +799,6 @@ test = ["pytest (>=6)"] name = "fastapi" version = "0.98.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -851,7 +817,6 @@ all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" name = "filelock" version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -867,7 +832,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "flake8" version = "6.0.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -884,7 +848,6 @@ pyflakes = ">=3.0.0,<3.1.0" name = "flake8-bandit" version = "4.1.1" description = "Automated security testing with bandit and flake8." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -900,7 +863,6 @@ flake8 = ">=5.0.0" name = "flake8-broken-line" version = "1.0.0" description = "Flake8 plugin to forbid backslashes for line breaks" -category = "dev" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -915,7 +877,6 @@ flake8 = ">5" name = "flake8-bugbear" version = "23.7.10" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -934,7 +895,6 @@ dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", name = "flake8-commas" version = "2.1.0" description = "Flake8 lint for trailing commas." -category = "dev" optional = false python-versions = "*" files = [ @@ -949,7 +909,6 @@ flake8 = ">=2" name = "flake8-comprehensions" version = "3.14.0" description = "A flake8 plugin to help you write better list/set/dict comprehensions." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -964,7 +923,6 @@ flake8 = ">=3.0,<3.2.0 || >3.2.0" name = "flake8-debugger" version = "4.1.2" description = "ipdb/pdb statement checker plugin for flake8" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -980,7 +938,6 @@ pycodestyle = "*" name = "flake8-docstrings" version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -996,7 +953,6 @@ pydocstyle = ">=2.1" name = "flake8-eradicate" version = "1.5.0" description = "Flake8 plugin to find commented out code" -category = "dev" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -1013,7 +969,6 @@ flake8 = ">5" name = "flake8-isort" version = "6.0.0" description = "flake8 plugin that integrates isort ." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1032,7 +987,6 @@ test = ["pytest"] name = "flake8-quotes" version = "3.3.2" description = "Flake8 lint for quotes." -category = "dev" optional = false python-versions = "*" files = [ @@ -1046,7 +1000,6 @@ flake8 = "*" name = "flake8-rst-docstrings" version = "0.3.0" description = "Python docstring reStructuredText (RST) validator for flake8" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1066,7 +1019,6 @@ develop = ["build", "twine"] name = "flake8-string-format" version = "0.3.0" description = "string format checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" files = [ @@ -1081,7 +1033,6 @@ flake8 = "*" name = "flupy" version = "1.2.0" description = "Method chaining built on generators" -category = "main" optional = false python-versions = "*" files = [ @@ -1098,7 +1049,6 @@ dev = ["black", "mypy", "pre-commit", "pylint", "pytest", "pytest-benchmark", "p name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1169,7 +1119,6 @@ files = [ name = "gitdb" version = "4.0.10" description = "Git Object Database" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1184,7 +1133,6 @@ smmap = ">=3.0.1,<6" name = "gitpython" version = "3.1.32" description = "GitPython is a Python library used to interact with Git repositories" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1199,7 +1147,6 @@ gitdb = ">=4.0.1,<5" name = "googleapis-common-protos" version = "1.59.1" description = "Common protobufs used in Google APIs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1217,7 +1164,6 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1291,7 +1237,6 @@ test = ["objgraph", "psutil"] name = "grpc-gateway-protoc-gen-openapiv2" version = "0.1.0" description = "Provides the missing pieces for gRPC Gateway." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1306,7 +1251,6 @@ googleapis-common-protos = "*" name = "grpcio" version = "1.56.2" description = "HTTP/2-based RPC framework" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1364,7 +1308,6 @@ protobuf = ["grpcio-tools (>=1.56.2)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1376,7 +1319,6 @@ files = [ name = "httpcore" version = "0.17.3" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1388,17 +1330,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httptools" version = "0.5.0" description = "A collection of framework independent HTTP protocol utils." -category = "main" optional = false python-versions = ">=3.5.0" files = [ @@ -1452,7 +1393,6 @@ test = ["Cython (>=0.29.24,<0.30.0)"] name = "httpx" version = "0.24.1" description = "The next generation HTTP client." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1468,15 +1408,14 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "identify" version = "2.5.25" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1491,7 +1430,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1503,7 +1441,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1515,7 +1452,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1533,7 +1469,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1545,7 +1480,6 @@ files = [ name = "kafka-python" version = "2.0.2" description = "Pure Python client for Apache Kafka" -category = "main" optional = false python-versions = "*" files = [ @@ -1560,7 +1494,6 @@ crc32c = ["crc32c"] name = "lanarky" version = "0.7.10" description = "Ship production-ready LLM projects with FastAPI" -category = "main" optional = false python-versions = ">=3.9,<4.0" files = [ @@ -1584,7 +1517,6 @@ redis = ["redis (>=4.5.5,<5.0.0)"] name = "langchain" version = "0.0.199" description = "Building applications with LLMs through composability" -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -1622,7 +1554,6 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"] name = "langchainplus-sdk" version = "0.0.20" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -1639,7 +1570,6 @@ tenacity = ">=8.1.0,<9.0.0" name = "loguru" version = "0.7.0" description = "Python logging made (stupidly) simple" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1658,7 +1588,6 @@ dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegu name = "lz4" version = "4.3.2" description = "LZ4 Bindings for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1708,7 +1637,6 @@ tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1733,7 +1661,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "marshmallow" version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1754,7 +1681,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-enum" version = "1.5.1" description = "Enum field for Marshmallow" -category = "main" optional = false python-versions = "*" files = [ @@ -1769,7 +1695,6 @@ marshmallow = ">=2.0.0" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1781,7 +1706,6 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1793,7 +1717,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1877,7 +1800,6 @@ files = [ name = "mypy" version = "1.4.1" description = "Optional static typing for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1924,7 +1846,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1936,7 +1857,6 @@ files = [ name = "mysqlclient" version = "2.2.0" description = "Python interface to MySQL" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1953,9 +1873,8 @@ files = [ name = "ndg-httpsclient" version = "0.5.1" description = "Provides enhanced HTTPS support for httplib and urllib2 using PyOpenSSL" -category = "main" optional = false -python-versions = ">=2.7,<3.0.0 || >=3.4.0" +python-versions = ">=2.7,<3.0.dev0 || >=3.4.dev0" files = [ {file = "ndg_httpsclient-0.5.1-py2-none-any.whl", hash = "sha256:d2c7225f6a1c6cf698af4ebc962da70178a99bcde24ee6d1961c4f3338130d57"}, {file = "ndg_httpsclient-0.5.1-py3-none-any.whl", hash = "sha256:dd174c11d971b6244a891f7be2b32ca9853d3797a72edb34fa5d7b07d8fff7d4"}, @@ -1970,7 +1889,6 @@ PyOpenSSL = "*" name = "networkx" version = "3.1" description = "Python package for creating and manipulating graphs and networks" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1989,7 +1907,6 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -2004,7 +1921,6 @@ setuptools = "*" name = "numexpr" version = "2.8.4" description = "Fast numerical expression evaluator for NumPy" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2047,7 +1963,6 @@ numpy = ">=1.13.3" name = "numpy" version = "1.25.1" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2082,7 +1997,6 @@ files = [ name = "openai" version = "0.27.8" description = "Python client library for the OpenAI API" -category = "main" optional = false python-versions = ">=3.7.1" files = [ @@ -2097,7 +2011,7 @@ tqdm = "*" [package.extras] datalib = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -dev = ["black (>=21.6b0,<22.0)", "pytest (>=6.0.0,<7.0.0)", "pytest-asyncio", "pytest-mock"] +dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-mock"] embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"] wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"] @@ -2105,7 +2019,6 @@ wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1 name = "openapi-schema-pydantic" version = "1.2.4" description = "OpenAPI (v3) specification schema as pydantic class" -category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -2120,7 +2033,6 @@ pydantic = ">=1.8.2" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2132,7 +2044,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2144,7 +2055,6 @@ files = [ name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" -category = "dev" optional = false python-versions = ">=2.6" files = [ @@ -2156,7 +2066,6 @@ files = [ name = "pep8-naming" version = "0.13.3" description = "Check PEP-8 naming conventions, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2171,7 +2080,6 @@ flake8 = ">=5.0.0" name = "pgvector" version = "0.1.8" description = "pgvector support for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2185,7 +2093,6 @@ numpy = "*" name = "pinecone-client" version = "2.2.2" description = "Pinecone client and SDK" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2216,7 +2123,6 @@ grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv name = "platformdirs" version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2232,7 +2138,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2248,7 +2153,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2267,7 +2171,6 @@ virtualenv = ">=20.10.0" name = "protobuf" version = "3.19.6" description = "Protocol Buffers" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2302,7 +2205,6 @@ files = [ name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2374,7 +2276,6 @@ files = [ name = "pusher" version = "3.3.2" description = "A Python library to interract with the Pusher Channels API" -category = "main" optional = false python-versions = "*" files = [ @@ -2399,7 +2300,6 @@ tornado = ["tornado (>=5.0.0)"] name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2411,7 +2311,6 @@ files = [ name = "pycodestyle" version = "2.10.0" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2423,7 +2322,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2435,7 +2333,6 @@ files = [ name = "pydantic" version = "1.10.11" description = "Data validation and settings management using python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2489,7 +2386,6 @@ email = ["email-validator (>=1.0.3)"] name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2507,7 +2403,6 @@ toml = ["tomli (>=1.2.3)"] name = "pyflakes" version = "3.0.1" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2519,7 +2414,6 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2534,7 +2428,6 @@ plugins = ["importlib-metadata"] name = "pymysql" version = "1.1.0" description = "Pure Python MySQL Driver" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2550,7 +2443,6 @@ rsa = ["cryptography"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2577,7 +2469,6 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2596,7 +2487,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pypdf2" version = "3.0.1" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2618,7 +2508,6 @@ image = ["Pillow"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2641,7 +2530,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2660,7 +2548,6 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2679,7 +2566,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2697,7 +2583,6 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2715,7 +2600,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2730,7 +2614,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2741,11 +2624,24 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-multipart" +version = "0.0.6" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"}, + {file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"}, +] + +[package.extras] +dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] + [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2795,7 +2691,6 @@ files = [ name = "regex" version = "2023.6.3" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2893,7 +2788,6 @@ files = [ name = "replicate" version = "0.8.4" description = "Python client for Replicate" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2913,7 +2807,6 @@ dev = ["black", "mypy", "pytest", "responses", "ruff"] name = "requests" version = "2.28.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -2935,7 +2828,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] name = "restructuredtext-lint" version = "1.4.0" description = "reStructuredText linter" -category = "dev" optional = false python-versions = "*" files = [ @@ -2949,7 +2841,6 @@ docutils = ">=0.11,<1.0" name = "rich" version = "13.4.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -2968,7 +2859,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "s3transfer" version = "0.6.1" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.7" files = [ @@ -2986,7 +2876,6 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] name = "sentry-sdk" version = "1.28.1" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" files = [ @@ -3029,7 +2918,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3046,7 +2934,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3058,7 +2945,6 @@ files = [ name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3070,7 +2956,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3082,7 +2967,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -3094,7 +2978,6 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3106,7 +2989,6 @@ files = [ name = "sqlalchemy" version = "2.0.19" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3154,7 +3036,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\""} +greenlet = {version = "!=0.4.17", optional = true, markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\" or extra == \"asyncio\""} mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""} typing-extensions = ">=4.2.0" @@ -3186,7 +3068,6 @@ sqlcipher = ["sqlcipher3-binary"] name = "starlette" version = "0.27.0" description = "The little ASGI library that shines." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3205,7 +3086,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "stevedore" version = "5.1.0" description = "Manage dynamic plugins for Python applications" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3220,7 +3100,6 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" name = "tenacity" version = "8.2.2" description = "Retry code until it succeeds" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3235,7 +3114,6 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "tiktoken" version = "0.4.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3281,7 +3159,6 @@ blobfile = ["blobfile (>=2)"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3293,7 +3170,6 @@ files = [ name = "tqdm" version = "4.65.0" description = "Fast, Extensible Progress Meter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3314,7 +3190,6 @@ telegram = ["requests"] name = "types-requests" version = "2.31.0.2" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = "*" files = [ @@ -3329,7 +3204,6 @@ types-urllib3 = "*" name = "types-urllib3" version = "1.26.25.14" description = "Typing stubs for urllib3" -category = "dev" optional = false python-versions = "*" files = [ @@ -3341,7 +3215,6 @@ files = [ name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3353,7 +3226,6 @@ files = [ name = "typing-inspect" version = "0.9.0" description = "Runtime inspection utilities for typing module." -category = "main" optional = false python-versions = "*" files = [ @@ -3369,7 +3241,6 @@ typing-extensions = ">=3.7.4" name = "ujson" version = "5.8.0" description = "Ultra fast JSON encoder and decoder for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3440,7 +3311,6 @@ files = [ name = "urllib3" version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -3457,7 +3327,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "uvicorn" version = "0.22.0" description = "The lightning-fast ASGI server." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3472,7 +3341,7 @@ h11 = ">=0.8" httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} @@ -3483,7 +3352,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", name = "uvloop" version = "0.17.0" description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3528,7 +3396,6 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my name = "validators" version = "0.20.0" description = "Python Data Validation for Humans™." -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -3545,7 +3412,6 @@ test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] name = "vecs" version = "0.2.6" description = "pgvector client" -category = "main" optional = false python-versions = "*" files = [ @@ -3553,10 +3419,10 @@ files = [ ] [package.dependencies] -flupy = ">=1.0.0,<2.0.0" -pgvector = ">=0.1.0,<0.2.0" -psycopg2-binary = ">=2.9.0,<2.10.0" -sqlalchemy = ">=2.0.0,<3.0.0" +flupy = "==1.*" +pgvector = "==0.1.*" +psycopg2-binary = "==2.9.*" +sqlalchemy = "==2.*" [package.extras] dev = ["numpy", "parse", "pytest", "pytest-cov"] @@ -3566,7 +3432,6 @@ docs = ["mkdocs", "pygments", "pymarkdown", "pymdown-extensions"] name = "virtualenv" version = "20.24.1" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3587,7 +3452,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "watchfiles" version = "0.19.0" description = "Simple, modern and high performance file watching and code reload in python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3622,7 +3486,6 @@ anyio = ">=3.0.0" name = "weaviate-client" version = "3.22.1" description = "A python native Weaviate client" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3643,7 +3506,6 @@ grpc = ["grpcio", "grpcio-tools"] name = "websockets" version = "11.0.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3723,7 +3585,6 @@ files = [ name = "wemake-python-styleguide" version = "0.18.0" description = "The strictest and most opinionated python linter ever" -category = "dev" optional = false python-versions = ">=3.8.1,<4.0" files = [ @@ -3757,7 +3618,6 @@ typing_extensions = ">=4.0,<5.0" name = "wikipedia" version = "1.4.0" description = "Wikipedia API for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -3772,7 +3632,6 @@ requests = ">=2.0.0,<3.0.0" name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -3787,7 +3646,6 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3874,4 +3732,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "dce3fd79c5c211f6fc90fbed5cdaca80d6b187c8064b49c82e2d65eee21906ee" +content-hash = "9a28c447921f41d7c04eefbe1551131b5f663330cdb48a123c62a6aca750f818" diff --git a/platform/pyproject.toml b/platform/pyproject.toml index 823160cb73..674671de3d 100644 --- a/platform/pyproject.toml +++ b/platform/pyproject.toml @@ -40,6 +40,7 @@ pinecone-client = { version = "^2.2.2", extras = ["grpc"] } networkx = "^3.1" pusher = "^3.3.2" pypdf2 = "^3.0.1" +python-multipart = "^0.0.6" [tool.poetry.dev-dependencies] diff --git a/platform/reworkd_platform/schemas/user.py b/platform/reworkd_platform/schemas/user.py index 2984b0cc60..a1180f8639 100644 --- a/platform/reworkd_platform/schemas/user.py +++ b/platform/reworkd_platform/schemas/user.py @@ -13,6 +13,7 @@ class UserBase(BaseModel): id: str name: Optional[str] email: Optional[str] + image: Optional[str] = Field(default=None) organization: Optional[OrganizationRole] = Field(default=None) @property diff --git a/platform/reworkd_platform/services/sockets.py b/platform/reworkd_platform/services/sockets.py index 40e637b1db..ebc93bbeae 100644 --- a/platform/reworkd_platform/services/sockets.py +++ b/platform/reworkd_platform/services/sockets.py @@ -5,6 +5,7 @@ from pusher.errors import PusherBadRequest from requests import ReadTimeout +from reworkd_platform.schemas import UserBase from reworkd_platform.settings import Settings, settings as app_settings @@ -24,5 +25,22 @@ def emit(self, channel: str, event: str, data: Dict[str, Any]) -> None: except (PusherBadRequest, ReadTimeout) as e: logger.warning(f"Failed to emit event: {data}") + def authenticate( + self, user: UserBase, channel: str, socket_id: str + ) -> Dict[str, Any]: + # TODO: should probably make sure the user is allowed to authenticate to each channel + return self._client and self._client.authenticate( + channel=channel, + socket_id=socket_id, + custom_data={ + "user_id": user.id, + "user_info": { + "name": user.name, + "email": user.email, + "image": user.image, + }, + }, + ) + websockets = WebsocketService(settings=app_settings) diff --git a/platform/reworkd_platform/web/api/auth/views.py b/platform/reworkd_platform/web/api/auth/views.py index da152bb496..9c28d4b1f5 100644 --- a/platform/reworkd_platform/web/api/auth/views.py +++ b/platform/reworkd_platform/web/api/auth/views.py @@ -1,6 +1,11 @@ -from fastapi import APIRouter, Depends, HTTPException +from typing import Annotated, Dict + +from fastapi import APIRouter, Depends, HTTPException, Form from reworkd_platform.db.crud.organization import OrganizationCrud, OrganizationUsers +from reworkd_platform.schemas import UserBase +from reworkd_platform.services.sockets import websockets +from reworkd_platform.web.api.dependencies import get_current_user router = APIRouter() @@ -30,3 +35,12 @@ async def organizations( # async def update_organization(organization_id: str): # """Update an organization by ID""" # pass + + +@router.post("/pusher") +async def pusher_authentication( + channel_name: Annotated[str, Form()], + socket_id: Annotated[str, Form()], + user: UserBase = Depends(get_current_user), +) -> Dict[str, str]: + return websockets.authenticate(user, channel_name, socket_id) From 25f5cb609f3ab83e69e170aae94cf3f199277528 Mon Sep 17 00:00:00 2001 From: awtkns <32209255+awtkns@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:41:39 -0700 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9A=80Realtime=20presence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platform/reworkd_platform/services/sockets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/reworkd_platform/services/sockets.py b/platform/reworkd_platform/services/sockets.py index ebc93bbeae..3da98ae370 100644 --- a/platform/reworkd_platform/services/sockets.py +++ b/platform/reworkd_platform/services/sockets.py @@ -28,8 +28,11 @@ def emit(self, channel: str, event: str, data: Dict[str, Any]) -> None: def authenticate( self, user: UserBase, channel: str, socket_id: str ) -> Dict[str, Any]: + if not self._client: + return {} + # TODO: should probably make sure the user is allowed to authenticate to each channel - return self._client and self._client.authenticate( + return self._client.authenticate( channel=channel, socket_id=socket_id, custom_data={ From 33b0848933e582ff98ed894177a5af1f6810ec01 Mon Sep 17 00:00:00 2001 From: awtkns <32209255+awtkns@users.noreply.github.com> Date: Sun, 23 Jul 2023 23:24:59 -0700 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=9A=80Fixup=20realtime=20presence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next/src/hooks/useSocket.ts | 15 +++--- next/src/hooks/useWorkflow.ts | 49 ++++++++++++------- platform/reworkd_platform/services/sockets.py | 2 +- .../services/worker/execution_engine.py | 4 +- .../web/api/workflow/views.py | 3 ++ 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/next/src/hooks/useSocket.ts b/next/src/hooks/useSocket.ts index 59ef410912..10d4149fec 100644 --- a/next/src/hooks/useSocket.ts +++ b/next/src/hooks/useSocket.ts @@ -27,11 +27,13 @@ const PresenceMemberEventSchema = z.object({ type PresenceInfo = z.infer; -export default function useSocket( +export default function useSocket( channelName: string, accessToken: string | undefined, - eventSchema: T, - callback: (data: z.infer) => void + callbacks: { + event: string; + callback: (data: unknown) => Promise | void; + }[] ) { const [members, setMembers] = useState>({}); @@ -51,9 +53,10 @@ export default function useSocket( }); const channel = pusher.subscribe("presence-" + channelName); - channel.bind("my-event", async (data) => { - const obj = (await eventSchema.parse(data)) as z.infer; - callback(obj); + callbacks.map(({ event, callback }) => { + channel.bind(event, async (data) => { + await callback(data); + }); }); channel.bind("pusher:subscription_succeeded", async (data) => { diff --git a/next/src/hooks/useWorkflow.ts b/next/src/hooks/useWorkflow.ts index d38c1ee2e9..2c4445f8c2 100644 --- a/next/src/hooks/useWorkflow.ts +++ b/next/src/hooks/useWorkflow.ts @@ -12,12 +12,16 @@ import { useWorkflowStore } from "../stores/workflowStore"; import type { NodeBlock, Workflow, WorkflowEdge, WorkflowNode } from "../types/workflow"; import { getNodeType, toReactFlowEdge, toReactFlowNode } from "../types/workflow"; -const eventSchema = z.object({ +const StatusEventSchema = z.object({ nodeId: z.string(), status: z.enum(["running", "success", "failure"]), remaining: z.number().optional(), }); +const SaveEventSchema = z.object({ + user_id: z.string(), +}); + const updateValue = < DATA extends WorkflowEdge | WorkflowNode, KEY extends keyof DATA, @@ -82,22 +86,31 @@ export const useWorkflow = (workflowId: string, session: Session | null) => { else setSelectedNode(selectedNodes[0]); }, [nodes]); - const members = useSocket( - workflowId, - session?.accessToken, - eventSchema, - ({ nodeId, status, remaining }) => { - updateValue(setNodes, "status", status, (n) => n?.id === nodeId); - updateValue(setEdges, "status", status, (e) => e?.target === nodeId); - - if (remaining === 0) { - setTimeout(() => { - updateValue(setNodes, "status", undefined); - updateValue(setEdges, "status", undefined); - }, 1000); - } - } - ); + const members = useSocket(workflowId, session?.accessToken, [ + { + event: "workflow:node:status", + callback: async (data) => { + const { nodeId, status, remaining } = await StatusEventSchema.parseAsync(data); + + updateValue(setNodes, "status", status, (n) => n?.id === nodeId); + updateValue(setEdges, "status", status, (e) => e?.target === nodeId); + + if (remaining === 0) { + setTimeout(() => { + updateValue(setNodes, "status", undefined); + updateValue(setEdges, "status", undefined); + }, 1000); + } + }, + }, + { + event: "workflow:updated", + callback: async (data) => { + const { user_id } = await SaveEventSchema.parseAsync(data); + if (user_id !== session?.user?.id) await refetchWorkflow(); + }, + }, + ]); const createNode: createNodeType = (block: NodeBlock) => { const ref = nanoid(11); @@ -151,6 +164,8 @@ export const useWorkflow = (workflowId: string, session: Session | null) => { })), file, }); + + // #TODO: WHY IS THIS NEEDED? await refetchWorkflow(); }; diff --git a/platform/reworkd_platform/services/sockets.py b/platform/reworkd_platform/services/sockets.py index 3da98ae370..f59fbee3be 100644 --- a/platform/reworkd_platform/services/sockets.py +++ b/platform/reworkd_platform/services/sockets.py @@ -21,7 +21,7 @@ def __init__(self, settings: Settings): def emit(self, channel: str, event: str, data: Dict[str, Any]) -> None: try: - self._client and self._client.trigger(channel, event, data) + self._client and self._client.trigger("presence-" + channel, event, data) except (PusherBadRequest, ReadTimeout) as e: logger.warning(f"Failed to emit event: {data}") diff --git a/platform/reworkd_platform/services/worker/execution_engine.py b/platform/reworkd_platform/services/worker/execution_engine.py index f9efedceaf..5dada2516f 100644 --- a/platform/reworkd_platform/services/worker/execution_engine.py +++ b/platform/reworkd_platform/services/worker/execution_engine.py @@ -35,7 +35,7 @@ async def loop(self) -> None: websockets.emit( self.workflow.workflow_id, - "my-event", + "workflow:node:status", { "nodeId": curr.id, "status": "running", @@ -61,7 +61,7 @@ async def loop(self) -> None: websockets.emit( self.workflow.workflow_id, - "my-event", + "workflow:node:status", { "nodeId": curr.id, "status": "success", diff --git a/platform/reworkd_platform/web/api/workflow/views.py b/platform/reworkd_platform/web/api/workflow/views.py index 32263fcb5b..f6835de265 100644 --- a/platform/reworkd_platform/web/api/workflow/views.py +++ b/platform/reworkd_platform/web/api/workflow/views.py @@ -11,6 +11,7 @@ from reworkd_platform.services.aws.s3 import PresignedPost, SimpleStorageService from reworkd_platform.services.kafka.producers.task_producer import WorkflowTaskProducer from reworkd_platform.services.networkx import validate_connected_and_acyclic +from reworkd_platform.services.sockets import websockets from reworkd_platform.services.worker.execution_engine import ExecutionEngine router = APIRouter() @@ -54,6 +55,8 @@ async def update_workflow( raise HTTPException(status_code=422, detail=str(e)) await crud.update(workflow_id, workflow) + websockets.emit(workflow_id, "workflow:updated", {"user_id": crud.user.id}) + return SimpleStorageService().upload_url( bucket_name="test-pdf-123", object_name=workflow_id,