From 8387460ec335a791270cbc19b8f6dd553834dc3e Mon Sep 17 00:00:00 2001 From: Felipe Rosa Date: Wed, 6 Sep 2023 14:51:22 -0300 Subject: [PATCH] chore: Update ideascale/snapshot importer to use pydantic BaseModel as well as make it compatible with pydantic v2 | NPG-8105 (#554) # Description * Ideascale and snapshot importers now use pydantic's `BaseModel` instead of `dataclasses.dataclass` * Replaced `parse_obj_raw` for `model_validate` as it was removed in pydantic v2 ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? I ran the importers manually. ## Checklist - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- services/voting-node/poetry.lock | 103 +++++++++++++++++- .../ideascale_importer/gvc.py | 12 +- .../ideascale_importer/ideascale/client.py | 33 +++--- .../ideascale_importer/ideascale/importer.py | 27 ++--- .../ideascale_importer/snapshot_importer.py | 95 +++++----------- .../ideascale_importer/utils.py | 9 +- utilities/ideascale-importer/pyproject.toml | 2 +- 7 files changed, 157 insertions(+), 124 deletions(-) diff --git a/services/voting-node/poetry.lock b/services/voting-node/poetry.lock index 7cf45c169e..847fb7b322 100644 --- a/services/voting-node/poetry.lock +++ b/services/voting-node/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "aiofile" version = "3.8.5" description = "Asynchronous file operations." +category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -21,6 +22,7 @@ develop = ["aiomisc-pytest", "coveralls", "pytest", "pytest-cov", "pytest-rst"] name = "aiohttp" version = "3.8.3" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -129,6 +131,7 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -143,6 +146,7 @@ frozenlist = ">=1.1.0" name = "annotated-types" version = "0.5.0" description = "Reusable constraint types to use with typing.Annotated" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -154,6 +158,7 @@ files = [ name = "anyio" version = "3.6.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.6.2" files = [ @@ -174,6 +179,7 @@ trio = ["trio (>=0.16,<0.22)"] name = "asgiref" version = "3.7.0" description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -188,6 +194,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -199,6 +206,7 @@ files = [ name = "asyncio" version = "3.4.3" description = "reference implementation of PEP 3156" +category = "main" optional = false python-versions = "*" files = [ @@ -212,6 +220,7 @@ files = [ name = "asyncpg" version = "0.27.0" description = "An asyncio PostgreSQL driver" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -262,6 +271,7 @@ test = ["flake8 (>=5.0.4,<5.1.0)", "uvloop (>=0.15.3)"] name = "asyncpg-stubs" version = "0.27.0" description = "asyncpg stubs" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -277,6 +287,7 @@ typing-extensions = ">=4.2.0,<5.0.0" name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -294,6 +305,7 @@ tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -305,6 +317,7 @@ files = [ name = "beautifulsoup4" version = "4.11.1" description = "Screen-scraping library" +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -323,6 +336,7 @@ lxml = ["lxml"] name = "black" version = "23.3.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -370,6 +384,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "brotlipy" version = "0.7.0" description = "Python binding to the Brotli library" +category = "main" optional = false python-versions = "*" files = [ @@ -428,6 +443,7 @@ cffi = ">=1.0.0" name = "caio" version = "0.9.12" description = "Asynchronous file IO for Linux MacOS or Windows." +category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -461,6 +477,7 @@ develop = ["aiomisc-pytest", "pytest", "pytest-cov"] name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -472,6 +489,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -548,6 +566,7 @@ pycparser = "*" name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -562,6 +581,7 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -576,6 +596,7 @@ 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 = [ @@ -587,6 +608,7 @@ files = [ name = "commonmark" version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" +category = "main" optional = false python-versions = "*" files = [ @@ -601,6 +623,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] name = "cryptography" version = "40.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -642,6 +665,7 @@ tox = ["tox"] name = "deprecated" version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -659,6 +683,7 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version name = "et-xmlfile" version = "1.1.0" description = "An implementation of lxml.xmlfile for the standard library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -670,6 +695,7 @@ files = [ name = "fastapi" version = "0.101.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -689,6 +715,7 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -772,6 +799,7 @@ files = [ name = "googleapis-common-protos" version = "1.59.0" description = "Common protobufs used in Google APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -789,6 +817,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0dev)"] name = "grpcio" version = "1.54.2" description = "HTTP/2-based RPC framework" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -846,6 +875,7 @@ protobuf = ["grpcio-tools (>=1.54.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 = [ @@ -857,6 +887,7 @@ files = [ 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 = [ @@ -908,8 +939,9 @@ test = ["Cython (>=0.29.24,<0.30.0)"] [[package]] name = "ideascale-importer" -version = "0.4.0" +version = "0.4.1" description = "" +category = "main" optional = false python-versions = "^3.11" files = [] @@ -958,6 +990,7 @@ url = "../../utilities/ideascale-importer" name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -969,6 +1002,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -980,6 +1014,7 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -997,6 +1032,7 @@ i18n = ["Babel (>=2.7)"] name = "loguru" version = "0.6.0" description = "Python logging made (stupidly) simple" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1015,6 +1051,7 @@ dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1122,6 +1159,7 @@ source = ["Cython (>=0.29.35)"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1146,6 +1184,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markdownify" version = "0.11.6" description = "Convert HTML to markdown." +category = "main" optional = false python-versions = "*" files = [ @@ -1161,6 +1200,7 @@ six = ">=1.15,<2" name = "markupsafe" version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1220,6 +1260,7 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1231,6 +1272,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1242,6 +1284,7 @@ files = [ name = "multidict" version = "6.0.3" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1325,6 +1368,7 @@ files = [ 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 = [ @@ -1336,6 +1380,7 @@ files = [ name = "openpyxl" version = "3.1.2" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1350,6 +1395,7 @@ et-xmlfile = "*" name = "opentelemetry-api" version = "1.16.0" description = "OpenTelemetry Python API" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1365,6 +1411,7 @@ setuptools = ">=16.0" name = "opentelemetry-distro" version = "0.37b0" description = "OpenTelemetry Python Distro" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1385,6 +1432,7 @@ otlp = ["opentelemetry-exporter-otlp (==1.16.0)"] name = "opentelemetry-exporter-otlp" version = "1.16.0" description = "OpenTelemetry Collector Exporters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1400,6 +1448,7 @@ opentelemetry-exporter-otlp-proto-http = "1.16.0" name = "opentelemetry-exporter-otlp-proto-grpc" version = "1.16.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1422,6 +1471,7 @@ test = ["pytest-grpc"] name = "opentelemetry-exporter-otlp-proto-http" version = "1.16.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1444,6 +1494,7 @@ test = ["responses (==0.22.0)"] name = "opentelemetry-instrumentation" version = "0.37b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1460,6 +1511,7 @@ wrapt = ">=1.0.0,<2.0.0" name = "opentelemetry-instrumentation-asgi" version = "0.37b0" description = "ASGI instrumentation for OpenTelemetry" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1482,6 +1534,7 @@ test = ["opentelemetry-instrumentation-asgi[instruments]", "opentelemetry-test-u name = "opentelemetry-instrumentation-asyncpg" version = "0.37b0" description = "OpenTelemetry instrumentation for AsyncPG" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1502,6 +1555,7 @@ test = ["opentelemetry-instrumentation-asyncpg[instruments]", "opentelemetry-tes name = "opentelemetry-instrumentation-fastapi" version = "0.37b0" description = "OpenTelemetry FastAPI Instrumentation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1524,6 +1578,7 @@ test = ["httpx (>=0.22,<1.0)", "opentelemetry-instrumentation-fastapi[instrument name = "opentelemetry-proto" version = "1.16.0" description = "OpenTelemetry Python Proto" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1538,6 +1593,7 @@ protobuf = ">=3.19,<5.0" name = "opentelemetry-sdk" version = "1.16.0" description = "OpenTelemetry Python SDK" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1555,6 +1611,7 @@ typing-extensions = ">=3.7.4" name = "opentelemetry-semantic-conventions" version = "0.37b0" description = "OpenTelemetry Semantic Conventions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1566,6 +1623,7 @@ files = [ name = "opentelemetry-util-http" version = "0.37b0" description = "Web util for OpenTelemetry" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1577,6 +1635,7 @@ files = [ name = "packaging" version = "22.0" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1588,6 +1647,7 @@ 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 = [ @@ -1599,6 +1659,7 @@ files = [ name = "pdoc" version = "13.1.1" description = "API Documentation for Python Projects" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1618,6 +1679,7 @@ dev = ["black", "hypothesis", "mypy", "pygments (>=2.14.0)", "pytest", "pytest-c name = "platformdirs" version = "3.5.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 = [ @@ -1633,6 +1695,7 @@ 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 = [ @@ -1648,6 +1711,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prometheus-client" version = "0.16.0" description = "Python client for the Prometheus monitoring system." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1662,6 +1726,7 @@ twisted = ["twisted"] name = "prometheus-fastapi-instrumentator" version = "5.11.2" description = "Instrument your FastAPI with Prometheus metrics." +category = "main" optional = false python-versions = ">=3.7.0,<4.0.0" files = [ @@ -1677,6 +1742,7 @@ prometheus-client = ">=0.8.0,<1.0.0" name = "protobuf" version = "4.23.1" description = "" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1699,6 +1765,7 @@ files = [ name = "pycodestyle" version = "2.10.0" description = "Python style guide checker" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1710,6 +1777,7 @@ 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 = [ @@ -1721,6 +1789,7 @@ files = [ name = "pydantic" version = "2.1.1" description = "Data validation using Python type hints" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1740,6 +1809,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.4.0" description = "" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1853,6 +1923,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.0.1" description = "passive checker of Python programs" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1864,6 +1935,7 @@ files = [ name = "pygments" version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1878,6 +1950,7 @@ plugins = ["importlib-metadata"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1898,6 +1971,7 @@ 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 = [ @@ -1916,6 +1990,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy 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 = [ @@ -1930,6 +2005,7 @@ cli = ["click (>=5.0)"] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1979,6 +2055,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2000,6 +2077,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2018,6 +2096,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "ruff" version = "0.0.254" description = "An extremely fast Python linter, written in Rust." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2044,6 +2123,7 @@ files = [ name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2060,6 +2140,7 @@ 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 = [ @@ -2071,6 +2152,7 @@ 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 = [ @@ -2082,6 +2164,7 @@ files = [ name = "soupsieve" version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2093,6 +2176,7 @@ files = [ name = "starlette" version = "0.27.0" description = "The little ASGI library that shines." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2110,6 +2194,7 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2121,6 +2206,7 @@ files = [ name = "typeguard" version = "2.13.3" description = "Run-time type checker for Python" +category = "main" optional = false python-versions = ">=3.5.3" files = [ @@ -2136,6 +2222,7 @@ test = ["mypy", "pytest", "typing-extensions"] name = "typer" version = "0.7.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2156,6 +2243,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2167,6 +2255,7 @@ files = [ name = "typing-inspect" version = "0.8.0" description = "Runtime inspection utilities for typing module." +category = "main" optional = false python-versions = "*" files = [ @@ -2182,6 +2271,7 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "2.0.2" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2199,6 +2289,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.23.2" description = "The lightning-fast ASGI server." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2213,7 +2304,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\""} @@ -2224,6 +2315,7 @@ 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 = [ @@ -2268,6 +2360,7 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my 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 = [ @@ -2302,6 +2395,7 @@ anyio = ">=3.0.0" 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 = [ @@ -2381,6 +2475,7 @@ files = [ 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 = [ @@ -2395,6 +2490,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -2479,6 +2575,7 @@ files = [ name = "yarl" version = "1.8.2" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ diff --git a/utilities/ideascale-importer/ideascale_importer/gvc.py b/utilities/ideascale-importer/ideascale_importer/gvc.py index c97ca86be2..64b7ca0764 100644 --- a/utilities/ideascale-importer/ideascale_importer/gvc.py +++ b/utilities/ideascale-importer/ideascale_importer/gvc.py @@ -1,21 +1,17 @@ """GVC API module.""" - -from pydantic.dataclasses import dataclass -import pydantic.tools +from pydantic import BaseModel from typing import List from ideascale_importer import utils -@dataclass -class DrepAttributes: +class DrepAttributes(BaseModel): """Represents DREP attributes from the GVC API.""" voting_key: str -@dataclass -class Drep: +class Drep(BaseModel): """Represents a DREP from the GVC API.""" id: int @@ -38,4 +34,4 @@ async def dreps(self) -> List[Drep]: if not isinstance(res, dict): raise utils.BadResponse() - return [pydantic.tools.parse_obj_as(Drep, e) for e in res["data"]] + return [Drep.model_validate(e) for e in res["data"]] diff --git a/utilities/ideascale-importer/ideascale_importer/ideascale/client.py b/utilities/ideascale-importer/ideascale_importer/ideascale/client.py index f71eadcb62..11a79b349a 100644 --- a/utilities/ideascale-importer/ideascale_importer/ideascale/client.py +++ b/utilities/ideascale-importer/ideascale_importer/ideascale/client.py @@ -2,16 +2,14 @@ import asyncio import json -from pydantic.dataclasses import dataclass -import pydantic.tools +from pydantic import BaseModel, Field from typing import Any, Iterable, List, Mapping from ideascale_importer import utils from ideascale_importer.utils import GetFailed -@dataclass -class Campaign: +class Campaign(BaseModel): """Represents a campaign from IdeaScale. (Contains only the fields that are used by the importer). @@ -25,8 +23,7 @@ class Campaign: campaign_url: str -@dataclass -class CampaignGroup: +class CampaignGroup(BaseModel): """Represents a campaign group from IdeaScale. (Contains only the fields that are used by the importer). @@ -37,8 +34,7 @@ class CampaignGroup: campaigns: List[Campaign] -@dataclass -class IdeaAuthorInfo: +class IdeaAuthorInfo(BaseModel): """Represents an author info from IdeaScale. (Contains only the fields that are used by the importer). @@ -47,8 +43,7 @@ class IdeaAuthorInfo: name: str -@dataclass -class Idea: +class Idea(BaseModel): """Represents an idea from IdeaScale. (Contains only the fields that are used by the importer). @@ -61,15 +56,14 @@ class Idea: author_info: IdeaAuthorInfo contributors: List[IdeaAuthorInfo] url: str - custom_fields_by_key: Mapping[str, str] = pydantic.Field(default={}) + custom_fields_by_key: Mapping[str, str] = Field(default={}) def contributors_name(self) -> List[str]: """Get the names of all contributors.""" return list(map(lambda c: c.name, self.contributors)) -@dataclass -class Stage: +class Stage(BaseModel): """Represents a stage from IdeaScale. (Contains only the fields that are used by the importer). @@ -81,8 +75,7 @@ class Stage: funnel_name: str -@dataclass -class Funnel: +class Funnel(BaseModel): """Represents a funnel from IdeaScale. (Contains only the fields that are used by the importer). @@ -125,7 +118,7 @@ async def campaigns(self, group_id: int) -> List[Campaign]: if "campaigns" in group: group_campaigns = [] for c in group["campaigns"]: - group_campaigns.append(pydantic.tools.parse_obj_as(Campaign, c)) + group_campaigns.append(Campaign.model_validate(c)) await asyncio.sleep(0) campaigns.extend(group_campaigns) @@ -138,7 +131,7 @@ async def campaign_groups(self) -> List[CampaignGroup]: campaign_groups: List[CampaignGroup] = [] for cg in res: - campaign_groups.append(pydantic.tools.parse_obj_as(CampaignGroup, cg)) + campaign_groups.append(CampaignGroup.model_validate(cg)) await asyncio.sleep(0) return campaign_groups @@ -149,7 +142,7 @@ async def campaign_ideas(self, campaign_id: int) -> List[Idea]: ideas = [] for i in res: - ideas.append(pydantic.tools.parse_obj_as(Idea, i)) + ideas.append(Idea.model_validate(i)) await asyncio.sleep(0) return ideas @@ -178,7 +171,7 @@ async def worker(d: WorkerData): res_ideas: List[Idea] = [] for i in res: - res_ideas.append(pydantic.tools.parse_obj_as(Idea, i)) + res_ideas.append(Idea.model_validate(i)) d.ideas.extend(res_ideas) @@ -214,7 +207,7 @@ async def campaign_group_ideas(self, group_id: int) -> List[Idea]: async def funnel(self, funnel_id: int) -> Funnel: """Get the funnel with the given id.""" res = await self._get(f"/a/rest/v1/funnels/{funnel_id}") - return pydantic.tools.parse_obj_as(Funnel, res) + return Funnel.model_validate(res) async def _get(self, path: str) -> Mapping[str, Any] | Iterable[Mapping[str, Any]]: """Execute a GET request on IdeaScale API.""" diff --git a/utilities/ideascale-importer/ideascale_importer/ideascale/importer.py b/utilities/ideascale-importer/ideascale_importer/ideascale/importer.py index 5c4545fd9b..1a2d8acf0e 100644 --- a/utilities/ideascale-importer/ideascale_importer/ideascale/importer.py +++ b/utilities/ideascale-importer/ideascale_importer/ideascale/importer.py @@ -3,11 +3,9 @@ import re import asyncpg import csv -from dataclasses import dataclass -import json from loguru import logger from markdownify import markdownify -import pydantic +from pydantic import BaseModel from typing import Any, Dict, List, Mapping, Optional, Union from .client import Campaign, CampaignGroup, Client, Idea @@ -17,8 +15,7 @@ FieldMapping = Union[str, List[str]] -@dataclass -class ProposalsFieldsMappingConfig: +class ProposalsFieldsMappingConfig(BaseModel): """Represents the available configuration fields used in proposal fields mapping.""" proposer_url: FieldMapping @@ -27,35 +24,32 @@ class ProposalsFieldsMappingConfig: public_key: FieldMapping -@dataclass -class ProposalsConfig: +class ProposalsConfig(BaseModel): """Represents the available configuration fields used in proposal processing.""" field_mappings: ProposalsFieldsMappingConfig extra_field_mappings: Mapping[str, FieldMapping] # noqa: F821 -@dataclass -class ProposalsScoresCsvConfig: +class ProposalsScoresCsvConfig(BaseModel): """Represents the available configuration fields for proposal scores from the CSV file.""" id_field: str score_field: str -@dataclass -class Config: +class Config(BaseModel): """Represents the available configuration fields.""" campaign_group_id: int stage_ids: List[int] proposals: ProposalsConfig proposals_scores_csv: ProposalsScoresCsvConfig - + @staticmethod def from_json(val: dict): """Load configuration from a JSON object.""" - return pydantic.tools.parse_obj_as(Config, val) + return Config.model_validate(val) class ReadProposalsScoresCsv(Exception): """Raised when the proposals impact scores csv cannot be read.""" @@ -165,8 +159,7 @@ def html_to_md(s: str) -> str: return markdownify(s, strip=tags_to_strip).strip() -@dataclass -class Reward: +class Reward(BaseModel): """Represents a reward.""" amount: int @@ -257,7 +250,7 @@ async def load_config(self): if len(res) == 0: raise Exception("Cannot find ideascale config in the event-db database") self.config = Config.from_json(res[0].value) - + async def connect(self, *args, **kwargs): """Connect to the database.""" if self.conn is None: @@ -310,7 +303,7 @@ async def run(self): proposal_count = 0 async with self.conn.transaction(): - inserted_objectives = await ideascale_importer.db.upsert_many(self.conn, objectives, conflict_cols=["id", "event"], pre_update_cols={"deleted": True}, pre_update_cond={"event": f"= {self.event_id}"}) + inserted_objectives = await ideascale_importer.db.upsert_many(self.conn, objectives, conflict_cols=["id", "event"], pre_update_cols={"deleted": True}, pre_update_cond={"event": f"= {self.event_id}"}) inserted_objectives_ix = {o.id: o for o in inserted_objectives} proposals_with_campaign_id = [(a.campaign_id, mapper.map_proposal(a, self.proposals_impact_scores)) for a in ideas] diff --git a/utilities/ideascale-importer/ideascale_importer/snapshot_importer.py b/utilities/ideascale-importer/ideascale_importer/snapshot_importer.py index d547113e49..376d7517e6 100644 --- a/utilities/ideascale-importer/ideascale_importer/snapshot_importer.py +++ b/utilities/ideascale-importer/ideascale_importer/snapshot_importer.py @@ -2,15 +2,14 @@ import asyncio import brotli -import dataclasses -from dataclasses import dataclass from datetime import datetime import json import os import re from typing import Dict, List, Tuple, Optional from loguru import logger -import pydantic.tools +import pydantic +from pydantic import BaseModel from ideascale_importer.gvc import Client as GvcClient import ideascale_importer.db @@ -18,52 +17,7 @@ from ideascale_importer.utils import run_cmd -@dataclass -class DbSyncDatabaseConfig: - """Configuration for the database containing data from dbsync.""" - - db_url: str - - -@dataclass -class SnapshotToolConfig: - """Configuration for snapshot_tool.""" - - path: str - - -@dataclass -class CatalystToolboxConfig: - """Configuration for catalyst-toolbox.""" - - path: str - - -@dataclass -class GvcConfig: - """Configuration for GVC API.""" - - api_url: str - - -@dataclass -class Config: - """Configuration for the snapshot importer.""" - - dbsync_database: DbSyncDatabaseConfig - snapshot_tool: SnapshotToolConfig - catalyst_toolbox: CatalystToolboxConfig - gvc: GvcConfig - - @staticmethod - def from_json_file(path: str) -> "Config": - """Load configuration from a JSON file.""" - with open(path) as f: - return pydantic.tools.parse_obj_as(Config, json.load(f)) - - -@dataclass -class Contribution: +class Contribution(BaseModel): """Represents a voting power contribution.""" reward_address: str @@ -71,8 +25,7 @@ class Contribution: value: int -@dataclass -class HIR: +class HIR(BaseModel): """Represents a HIR.""" voting_group: str @@ -80,16 +33,14 @@ class HIR: voting_power: int -@dataclass -class SnapshotProcessedEntry: +class SnapshotProcessedEntry(BaseModel): """Represents a processed entry from snapshot_tool.""" contributions: List[Contribution] hir: HIR -@dataclass -class Registration: +class Registration(BaseModel): """Represents a voter registration.""" delegations: List[Tuple[str, int]] | str @@ -99,8 +50,7 @@ class Registration: voting_purpose: Optional[int] -@dataclass -class CatalystToolboxDreps: +class CatalystToolboxDreps(BaseModel): """Represents the input format of the dreps file of catalyst-toolbox.""" reps: List[str] @@ -160,8 +110,7 @@ class MissingNetworkSnapshotData(Exception): ... -@dataclass -class NetworkParams: +class NetworkParams(BaseModel): lastest_block_time: Optional[datetime] latest_block_slot_no: Optional[int] registration_snapshot_slot: Optional[int] @@ -177,8 +126,7 @@ class NetworkParams: } -@dataclass -class SSHConfig: +class SSHConfig(BaseModel): """Required SSH configuration values.""" keyfile_path: str @@ -369,11 +317,11 @@ async def _fetch_gvc_dreps_list(self): logger.error("Failed to get dreps, using drep cache", error=str(e)) await gvc_client.close() - self.dreps_json = json.dumps([dataclasses.asdict(d) for d in dreps]) + self.dreps_json = json.dumps([d.model_dump() for d in dreps]) dreps_data = CatalystToolboxDreps(reps=[d.attributes.voting_key for d in dreps]) with open(self.dreps_out_file, "w") as f: - json.dump(dataclasses.asdict(dreps_data), f) + json.dump(dreps_data.model_dump(), f) def _split_raw_snapshot_file(self, raw_snapshot_file: str): logger.info("Splitting raw snapshot file for processing") @@ -507,13 +455,22 @@ async def _write_db_data(self): with open(self.merged_catalyst_toolbox_out_file) as f: catalyst_toolbox_data_raw_json = f.read() - catalyst_toolbox_data: Dict[str, List[SnapshotProcessedEntry]] = pydantic.tools.parse_raw_as( - Dict[str, List[SnapshotProcessedEntry]], catalyst_toolbox_data_raw_json - ) + catalyst_toolbox_data_dict = json.loads(catalyst_toolbox_data_raw_json) + catalyst_toolbox_data: Dict[str, List[SnapshotProcessedEntry]] = {} + for k, entries in catalyst_toolbox_data_dict.items(): + try: + catalyst_toolbox_data[k] = [SnapshotProcessedEntry.model_validate(e) for e in entries] + except Exception as e: + logger.error(f"ERROR: {repr(e)}") + + snapshot_tool_data_dict = json.loads(snapshot_tool_data_raw_json) + snapshot_tool_data: Dict[str, List[Registration]] = {} + for k, entries in snapshot_tool_data_dict.items(): + try: + snapshot_tool_data[k] = [Registration.model_validate(e) for e in entries] + except Exception as e: + logger.error(f"ERROR: {repr(e)}") - snapshot_tool_data: Dict[str, List[Registration]] = pydantic.tools.parse_raw_as( - Dict[str, List[Registration]], snapshot_tool_data_raw_json - ) total_registered_voting_power = {} registration_delegation_data = {} diff --git a/utilities/ideascale-importer/ideascale_importer/utils.py b/utilities/ideascale-importer/ideascale_importer/utils.py index 803fbee1f2..1cd16d15d9 100644 --- a/utilities/ideascale-importer/ideascale_importer/utils.py +++ b/utilities/ideascale-importer/ideascale_importer/utils.py @@ -1,17 +1,15 @@ """Utility functions and classes.""" -from dataclasses import dataclass from datetime import datetime import sys import aiohttp import asyncio import json -import csv from loguru import logger import re from typing import Any, Dict, Iterable, List, TypeVar, TYPE_CHECKING +from pydantic import BaseModel -from .db.models import Model DictOrList = TypeVar("DictOrList", Dict[str, Any], List[Any]) @@ -76,8 +74,7 @@ async def run_cmd(name: str, cmd: str): logger.info("Successfully ran command") -@dataclass -class RequestProgressInfo: +class RequestProgressInfo(BaseModel): """Information about a request's progress.""" method: str @@ -96,7 +93,7 @@ def __init__(self): def request_start(self, req_id: int, method: str, url: str): """Register the start of a request.""" logger.info("Request started", req_id=req_id, method=method, url=url) - self.inflight_requests[req_id] = RequestProgressInfo(method, url, 0, datetime.now()) + self.inflight_requests[req_id] = RequestProgressInfo(method=method, url=url, bytes_received=0, last_update=datetime.now()) def request_progress(self, req_id: int, bytes_received: int): """Register the progress of a request.""" diff --git a/utilities/ideascale-importer/pyproject.toml b/utilities/ideascale-importer/pyproject.toml index 7bf879a057..cafafe9fe9 100644 --- a/utilities/ideascale-importer/pyproject.toml +++ b/utilities/ideascale-importer/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ideascale-importer" -version = "0.4.0" +version = "0.4.1" description = "" authors = [] readme = "README.md"