aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Jenkinsfile41
-rw-r--r--generalresearch/config.py18
-rw-r--r--generalresearch/managers/leaderboard/__init__.py4
-rw-r--r--generalresearch/models/custom_types.py41
-rw-r--r--pyproject.toml1
-rw-r--r--test_utils/conftest.py205
-rw-r--r--test_utils/incite/conftest.py3
-rw-r--r--tests/incite/collections/test_df_collection_item_thl_web.py26
-rw-r--r--tests/incite/collections/test_df_collection_thl_marketplaces.py2
-rw-r--r--tests/incite/collections/test_df_collection_thl_web.py27
-rw-r--r--tests/models/custom_types/test_aware_datetime.py2
-rw-r--r--tests/pytest.ini3
-rw-r--r--tests/test_postgres.py68
14 files changed, 255 insertions, 189 deletions
diff --git a/.gitignore b/.gitignore
index c7c1d0b..db79a59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ generalresearch/resources/brokerage_trust_calculated.csv
tests/.env.test
.env.*
.DS_Store
-build/ \ No newline at end of file
+build/
+*.egg-info \ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index a684caf..e829ba9 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -32,47 +32,6 @@ pipeline {
stages {
stage('Setup DB') {
- steps {
- script {
- env.DB_NAME = 'unittest-thl-' + UUID.randomUUID().toString().replace('-', '').take(12)
- env.THL_WEB_RW_DB = "postgres://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_POSTGRESQL_HOST}/${env.DB_NAME}"
- env.THL_WEB_RR_DB = env.THL_WEB_RW_DB
- env.THL_WEB_RO_DB = env.THL_WEB_RW_DB
- echo "Using database: ${env.DB_NAME}"
-
- env.SPECTRUM_DB_NAME = 'unittest-thl-spectrum-' + UUID.randomUUID().toString().replace('-', '').take(12)
- env.SPECTRUM_RW_DB = "mariadb://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_MARIA_HOST}/${env.SPECTRUM_DB_NAME}"
- env.SPECTRUM_RR_DB = env.SPECTRUM_RW_DB
- echo "Using database: ${env.SPECTRUM_DB_NAME}"
-
- env.GRLIQ_DB_NAME = 'unittest-grliq-' + UUID.randomUUID().toString().replace('-', '').take(12)
- env.GRLIQ_DB = "postgres://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_POSTGRESQL_HOST}/${env.GRLIQ_DB_NAME}"
- echo "Using database: ${env.GRLIQ_DB_NAME}"
-
- env.GR_DB_NAME = 'unittest-gr-' + UUID.randomUUID().toString().replace('-', '').take(12)
- env.GR_DB = "postgres://${env.DB_USER}:${env.DB_PASSWORD}@${env.DB_POSTGRESQL_HOST}/${env.GR_DB_NAME}"
- echo "Using database: ${env.GR_DB_NAME}"
- }
-
- sh """
- PGPASSWORD=${env.DB_PASSWORD} psql -h ${env.DB_POSTGRESQL_HOST} -U ${env.DB_USER} -d postgres <<EOF
- CREATE DATABASE "${env.DB_NAME}" WITH TEMPLATE = template0 ENCODING = 'UTF8';
- EOF
- """
- sh """
- PGPASSWORD=${env.DB_PASSWORD} psql -h ${env.DB_POSTGRESQL_HOST} -U ${env.DB_USER} -d postgres <<EOF
- CREATE DATABASE "${env.GRLIQ_DB_NAME}" WITH TEMPLATE = template0 ENCODING = 'UTF8';
- EOF
- """
- sh """
- PGPASSWORD=${env.DB_PASSWORD} psql -h ${env.DB_POSTGRESQL_HOST} -U ${env.DB_USER} -d postgres <<EOF
- CREATE DATABASE "${env.GR_DB_NAME}" WITH TEMPLATE = template0 ENCODING = 'UTF8';
- EOF
- """
- sh """
- mysql -h ${env.DB_MARIA_HOST} -u ${env.DB_USER} -p${env.DB_PASSWORD} --ssl=0 -e 'CREATE DATABASE `${env.SPECTRUM_DB_NAME}`;'
- """
-
script {
env.REDIS_DB = new Random().nextInt(1024).toString()
env.REDIS = "${env.REDIS}:6379/${env.REDIS_DB}"
diff --git a/generalresearch/config.py b/generalresearch/config.py
index 551f75a..af80069 100644
--- a/generalresearch/config.py
+++ b/generalresearch/config.py
@@ -5,9 +5,9 @@ from datetime import datetime, timezone
from pathlib import Path
from pydantic import DirectoryPath, Field, MariaDBDsn, PostgresDsn, RedisDsn
-from pydantic_settings import BaseSettings
+from pydantic_settings import BaseSettings, SettingsConfigDict
-from generalresearch.models.custom_types import DaskDsn, SentryDsn
+from generalresearch.models.custom_types import DaskDsn, InternalHostname, SentryDsn
os.environ["DISABLE_PANDERA_IMPORT_WARNING"] = "True"
@@ -39,8 +39,22 @@ def is_debug() -> bool:
class GRLBaseSettings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_file=(".env.test", ".env.testing", ".env.staging", ".env.prod"),
+ env_file_encoding="utf-8",
+ extra="allow",
+ )
+
debug: bool = Field(default=True)
+ # --- Pytest ---
+
+ testing_postgres: InternalHostname | None = Field(default=None)
+ testing_postgres_user: str | None = Field(default=None)
+ testing_postgres_pass: str | None = Field(default=None)
+
+ # ---
+
redis: RedisDsn | None = Field(default=None)
redis_timeout: float = Field(default=0.10)
diff --git a/generalresearch/managers/leaderboard/__init__.py b/generalresearch/managers/leaderboard/__init__.py
index 8468cdc..aae4a05 100644
--- a/generalresearch/managers/leaderboard/__init__.py
+++ b/generalresearch/managers/leaderboard/__init__.py
@@ -1,8 +1,8 @@
from typing import Dict
-from zoneinfo import ZoneInfo
import pytz
-from cachetools import cached, LRUCache
+from cachetools import LRUCache, cached
+from zoneinfo import ZoneInfo
@cached(cache=LRUCache(maxsize=1))
diff --git a/generalresearch/models/custom_types.py b/generalresearch/models/custom_types.py
index ea96741..84bf8e3 100644
--- a/generalresearch/models/custom_types.py
+++ b/generalresearch/models/custom_types.py
@@ -1,6 +1,10 @@
+from __future__ import annotations
+
import json
+import re
+import sys as _sys
from datetime import datetime, timedelta, timezone
-from typing import Any, Literal, Optional, Set
+from typing import Any, Literal
from uuid import UUID
from pydantic import (
@@ -14,14 +18,31 @@ from pydantic import (
)
from pydantic.functional_serializers import PlainSerializer
from pydantic.functional_validators import AfterValidator, BeforeValidator
-from pydantic.networks import UrlConstraints, IPvAnyNetwork
-from pydantic_core import Url
+from pydantic.networks import IPvAnyNetwork, UrlConstraints
+from pydantic_core import MultiHostHost, Url
from typing_extensions import Annotated
from generalresearch.models import DeviceType, Source
-# if TYPE_CHECKING:
-# from generalresearch.models import DeviceType
+HOSTNAME_REGEX = re.compile(
+ r"^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
+)
+
+
+def validate_hostname(v: str) -> str:
+ if not HOSTNAME_REGEX.match(v):
+ raise ValueError("Invalid internal hostname format")
+ return v
+
+
+InternalHostname = Annotated[str, AfterValidator(validate_hostname)]
+
+
+class PostgresDict(MultiHostHost):
+ """The path part of this host, or `None`."""
+
+ # Databasename
+ name: str | None
def convert_datetime_to_iso_8601_with_z_suffix(dt: datetime) -> str:
@@ -31,7 +52,7 @@ def convert_datetime_to_iso_8601_with_z_suffix(dt: datetime) -> str:
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
-def convert_str_dt(v: Any) -> Optional[AwareDatetime]:
+def convert_str_dt(v: Any) -> AwareDatetime | None:
# By default, pydantic is unable to handle tz-aware isoformat str. Attempt
# to parse a str that was dumped using the iso8601 format with Z suffix.
if v is not None and type(v) is str:
@@ -158,7 +179,7 @@ from_comma_sep_str = BeforeValidator(
# This is a set of DeviceType, that serializes and de-serializes into a
# (sorted) comma-separated str
-DeviceTypes = Annotated[Set[DeviceType], enum_to_comma_sep_str, from_comma_sep_str]
+DeviceTypes = Annotated[set[DeviceType], enum_to_comma_sep_str, from_comma_sep_str]
# This is a set of alphanumeric strings, that serializes and de-serializes
# into a (sorted) comma-separated str
@@ -223,9 +244,9 @@ InfluxDsn = Annotated[
),
]
-AlphaNumStrSet = Annotated[Set[AlphaNumStr], to_comma_sep_str, from_comma_sep_str]
-IPLikeStrSet = Annotated[Set[IPLikeStr], to_comma_sep_str, from_comma_sep_str]
-UUIDStrSet = Annotated[Set[UUIDStr], to_comma_sep_str, from_comma_sep_str]
+AlphaNumStrSet = Annotated[set[AlphaNumStr], to_comma_sep_str, from_comma_sep_str]
+IPLikeStrSet = Annotated[set[IPLikeStr], to_comma_sep_str, from_comma_sep_str]
+UUIDStrSet = Annotated[set[UUIDStr], to_comma_sep_str, from_comma_sep_str]
list_models_to_json_str = PlainSerializer(
lambda x: json.dumps([y.model_dump(mode="json") for y in x]),
diff --git a/pyproject.toml b/pyproject.toml
index 79b2382..dd2e649 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,6 +27,7 @@ dependencies = [
"pytest",
"pylibmc",
"pymemcache",
+ "pytz",
"redis",
"requests",
"scipy",
diff --git a/test_utils/conftest.py b/test_utils/conftest.py
index 232c1fc..9c80065 100644
--- a/test_utils/conftest.py
+++ b/test_utils/conftest.py
@@ -1,22 +1,20 @@
import os
import shutil
-import sys
from datetime import datetime, timedelta, timezone
from os.path import join as pjoin
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Generator
from uuid import uuid4
-import django
import pytest
import redis
from _pytest.config import Config
-from django.conf import settings as django_settings
-from django.core.management import call_command
from dotenv import load_dotenv
-from pydantic import MariaDBDsn, PostgresDsn
+from pydantic import MariaDBDsn, PostgresDsn, TypeAdapter
+from pydantic_core import MultiHostHost
from redis import Redis
+from generalresearch.models.custom_types import InternalHostname, PostgresDict
from generalresearch.pg_helper import PostgresConfig
from generalresearch.redis_helper import RedisConfig
from generalresearch.sql_helper import SqlHelper
@@ -28,7 +26,7 @@ if TYPE_CHECKING:
@pytest.fixture(scope="session")
-def env_file_path(pytestconfig: Config) -> str:
+def env_file_path(pytestconfig: Config) -> Path:
root_path = pytestconfig.rootpath
env_file = ".env.test"
@@ -40,18 +38,16 @@ def env_file_path(pytestconfig: Config) -> str:
for env_path in candidates:
if os.path.exists(env_path):
load_dotenv(dotenv_path=env_path, override=True)
- return os.path.normpath(env_path)
+ return Path(os.path.normpath(env_path))
raise AssertionError(f"No .env.test file found in: {', '.join(candidates)}")
@pytest.fixture(scope="session")
-def settings(env_file_path: str) -> "GRLBaseSettings":
+def settings(env_file_path: Path) -> "GRLBaseSettings":
from generalresearch.config import GRLBaseSettings
- print(f"{env_file_path=}")
-
- s = GRLBaseSettings(_env_file=env_file_path)
+ s = GRLBaseSettings()
if s.thl_mkpl_rr_db is not None:
if s.spectrum_rw_db is None:
@@ -71,14 +67,28 @@ def settings(env_file_path: str) -> "GRLBaseSettings":
def postgres_instance(settings: "GRLBaseSettings") -> Generator[PostgresDsn]:
"""Create a ephemeral postgresql instance for us to use during pytest.
- This is simplified, and only based off a single host. We don't want to
- create multiple migrated tmp databases for each rw/rr/ro connection
+ This does not create any tables, or schema definitions within the instance.
+ What this does is simply:
+
+ 1. Create a database on a known, consistent, staging or unittest
+ defined Postgres server.
+
+ 2. Return the PostgresDsn of that table
+
+ 3. On shutdown, go ahead and delete that database after the
+ tests have finished.
"""
- assert settings.thl_web_rw_db
- # assert settings.thl_web_rw_db.host
+ msg = "Must define Postgres test settings"
+ assert settings.testing_postgres, msg
+ assert settings.testing_postgres_user, msg
+ assert settings.testing_postgres_pass, msg
- dsn: PostgresDsn = settings.thl_web_rw_db
+ db_uri, db_user, db_pass = (
+ settings.testing_postgres,
+ settings.testing_postgres_user,
+ settings.testing_postgres_pass,
+ )
# Connect to default DB to create the new one
from psycopg import connect
@@ -86,40 +96,74 @@ def postgres_instance(settings: "GRLBaseSettings") -> Generator[PostgresDsn]:
now = datetime.now(timezone.utc)
ts: str = now.strftime("%Y-%m-%d")
-
db_name = f"unittest-{ts}-{uuid4().hex[:6]}"
- print("XXX", str(dsn))
- conn = connect(str(dsn))
+
+ db_path_connect = f"postgres://{db_user}:{db_pass}@{db_uri}"
+ db_path = f"{db_path_connect}/{db_name}"
+
+ # The DATABASE does NOT yet exist on the Postgres SERVER, thus
+ # we first must connect only to the SERVER (eg: default postgres path used)
+ conn = connect(f"{db_path_connect}/postgres")
conn.autocommit = True
cur = conn.cursor()
cur.execute(SQL("CREATE DATABASE {}").format(Identifier(db_name)))
cur.close()
conn.close()
- host = dsn.hosts()[0]
- db_url = (
- f"postgres://{host['username']}:{host['password']}@{host['host']}/{db_name}"
- )
-
- yield PostgresDsn(db_url)
+ yield PostgresDsn(db_path)
# Teardown: drop the DB after the session
- conn = connect(str(dsn))
+ conn = connect(f"{db_path_connect}/postgres")
conn.autocommit = True
cur = conn.cursor()
- # cur.execute(SQL("DROP DATABASE {}").format(Identifier(db_name)))
+ cur.execute(SQL("DROP DATABASE {} WITH (FORCE)").format(Identifier(db_name)))
cur.close()
conn.close()
@pytest.fixture(scope="session")
-def django_db_setup(settings: "GRLBaseSettings") -> Callable[..., None]:
+def postgres_instance_dict(
+ postgres_instance: PostgresDsn,
+) -> Generator[PostgresDict]:
+ host = postgres_instance.hosts()[0]
+ assert host is not None
+
+ msg = "Must have full Postgres details"
+ assert host["host"], msg
+ assert host["username"], msg
+ assert host["password"], msg
+
+ assert postgres_instance.path
+
+ yield PostgresDict(
+ username=host["username"],
+ password=host["password"],
+ host=host["host"],
+ name=postgres_instance.path.lstrip("/"),
+ port=5432,
+ )
+
+
+@pytest.fixture(scope="session")
+def postgres_instance_host(
+ postgres_instance_dict: PostgresDict,
+) -> Generator[InternalHostname]:
+ adapter = TypeAdapter(InternalHostname)
+ value = adapter.validate_python(postgres_instance_dict["host"])
+ yield value
+
+
+@pytest.fixture(scope="session")
+def django_db_factory(
+ postgres_instance: PostgresDsn, postgres_instance_dict: PostgresDict
+) -> Callable[..., PostgresDsn]:
- def _inner():
+ import django
+ from django.apps import apps
+ from django.conf import settings as django_settings
+ from django.core.management import call_command
- assert settings.thl_web_rw_db
- dsn: PostgresDsn = settings.thl_web_rw_db
- host = dsn.hosts()[0]
+ def _inner(django_project: str = "generalresearch.thl_django"):
# 1. Bootstrapping Django settings
if not django_settings.configured:
@@ -127,93 +171,70 @@ def django_db_setup(settings: "GRLBaseSettings") -> Callable[..., None]:
DATABASES={
"default": {
"ENGINE": "django.db.backends.postgresql",
- # PostgresDsn stores path as "/dbname"
- "NAME": str(dsn.path).lstrip("/"),
- "USER": host["username"],
- "PASSWORD": host["password"],
- "HOST": host["host"],
- "PORT": "5432",
+ "NAME": postgres_instance_dict["name"],
+ "USER": postgres_instance_dict["username"],
+ "PASSWORD": postgres_instance_dict["password"],
+ "HOST": postgres_instance_dict["host"],
+ "PORT": postgres_instance_dict["port"],
}
},
INSTALLED_APPS=[
"django.contrib.postgres",
"django.contrib.contenttypes",
- "generalresearch.thl_django",
+ django_project,
],
)
django.setup()
- from django.apps import apps
-
- for model in apps.get_models():
- print(f"Discovered model: {model._meta.label}")
+ # for model in apps.get_models():
+ # print(f"Discovered model: {model._meta.label}")
# 2. Run migrations directly during fixture activation
call_command("migrate")
+ # 3. Return the Dsn so the factory gives a way to connect
+ return postgres_instance
+
return _inner
@pytest.fixture(scope="session")
-def thl_web_rr(
- settings: "GRLBaseSettings", postgres_instance: PostgresDsn, django_db_setup
-) -> PostgresConfig:
- dsn = settings.thl_web_rr_db
- assert dsn
- assert dsn.path
-
- if dsn.path not in ["/", "/postgres"]:
- assert "/unittest-" in dsn.path
-
- db_path = postgres_instance.path
- host = dsn.hosts()[0]
- db_url = f"postgres://{host['username']}:{host['password']}@{host['host']}{db_path}"
-
- # Run Migrations now.
- django_db_setup()
+def thl_web_rr(django_db_factory: Callable[..., PostgresDsn]) -> PostgresConfig:
return PostgresConfig(
- dsn=PostgresDsn(db_url),
+ dsn=django_db_factory("generalresearch.thl_django"),
connect_timeout=1,
statement_timeout=5,
)
@pytest.fixture(scope="session")
-def thl_web_rw(
- settings: "GRLBaseSettings", postgres_instance: PostgresDsn, django_db_setup
-) -> PostgresConfig:
- dsn = settings.thl_web_rw_db
- assert dsn
- assert dsn.path
+def thl_web_rw(thl_web_rr: PostgresConfig) -> PostgresConfig:
+ return thl_web_rr
- if dsn.path not in ["/", "/postgres"]:
- assert "/unittest-" in dsn.path
- db_path = postgres_instance.path
- host = dsn.hosts()[0]
- db_url = f"postgres://{host['username']}:{host['password']}@{host['host']}{db_path}"
-
- # Run Migrations now.
- django_db_setup()
+@pytest.fixture(scope="session")
+def gr_db(django_db_factory: Callable[..., PostgresDsn]) -> PostgresConfig:
return PostgresConfig(
- dsn=PostgresDsn(db_url),
+ dsn=django_db_factory("gr_carer"),
connect_timeout=1,
statement_timeout=5,
)
@pytest.fixture(scope="session")
-def gr_db(settings: "GRLBaseSettings") -> PostgresConfig:
- dsn = settings.gr_db
- assert dsn
- assert dsn.path
+def grliq_db(postgres_instance: PostgresDsn) -> PostgresConfig:
- if dsn.path not in ["/", "/postgres"]:
- assert "/unittest-" in dsn.path
+ # test_words = {"localhost", "127.0.0.1", "unittest", "grliq-test"}
+ # assert any(w in str(postgres_config.dsn) for w in test_words), "check grliq postgres_config"
+ # assert "grliqdeceezpocymo" not in str(postgres_config.dsn), "check grliq postgres_config"
- return PostgresConfig(dsn=settings.gr_db, connect_timeout=5, statement_timeout=2)
+ return PostgresConfig(
+ dsn=postgres_instance,
+ connect_timeout=1,
+ statement_timeout=5,
+ )
@pytest.fixture(scope="session")
@@ -234,26 +255,6 @@ def spectrum_rw(settings: "GRLBaseSettings") -> SqlHelper:
@pytest.fixture(scope="session")
-def grliq_db(settings: "GRLBaseSettings") -> PostgresConfig:
- dsn = settings.grliq_db
- assert dsn
- assert dsn.path
-
- if dsn.path not in ["/", "/postgres"]:
- assert "/unittest-" in dsn.path
-
- # test_words = {"localhost", "127.0.0.1", "unittest", "grliq-test"}
- # assert any(w in str(postgres_config.dsn) for w in test_words), "check grliq postgres_config"
- # assert "grliqdeceezpocymo" not in str(postgres_config.dsn), "check grliq postgres_config"
-
- return PostgresConfig(
- dsn=settings.grliq_db,
- connect_timeout=2,
- statement_timeout=2,
- )
-
-
-@pytest.fixture(scope="session")
def thl_redis(settings: "GRLBaseSettings") -> "Redis":
# todo: this should get replaced with redisconfig (in most places)
# I'm not sure where this would be? in the domain name?
diff --git a/test_utils/incite/conftest.py b/test_utils/incite/conftest.py
index 058093e..0e2f7bd 100644
--- a/test_utils/incite/conftest.py
+++ b/test_utils/incite/conftest.py
@@ -10,9 +10,6 @@ import pytest
from _pytest.fixtures import SubRequest
from faker import Faker
-# from test_utils.managers.ledger.conftest import session_with_tx_factory
-# from test_utils.models.conftest import session_factory
-
if TYPE_CHECKING:
from generalresearch.config import GRLBaseSettings
from generalresearch.incite.base import GRLDatasets
diff --git a/tests/incite/collections/test_df_collection_item_thl_web.py b/tests/incite/collections/test_df_collection_item_thl_web.py
index a858fbe..8b8bcbe 100644
--- a/tests/incite/collections/test_df_collection_item_thl_web.py
+++ b/tests/incite/collections/test_df_collection_item_thl_web.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+from collections.abc import Generator
from datetime import datetime, timedelta, timezone
from itertools import product as iter_product
from os.path import join as pjoin
@@ -12,13 +15,7 @@ from distributed import Client, Scheduler, Worker
# noinspection PyUnresolvedReferences
from distributed.utils_test import (
- cleanup,
- client,
- client_no_amm,
- cluster_fixture,
gen_cluster,
- loop,
- loop_in_thread,
)
from faker import Faker
from pandera.pandas import DataFrameSchema
@@ -34,7 +31,6 @@ from generalresearch.models.thl.product import Product
from generalresearch.models.thl.user import User
from generalresearch.pg_helper import PostgresConfig
from generalresearch.sql_helper import PostgresDsn
-from test_utils.incite.conftest import incite_item_factory, mnt_filepath
if TYPE_CHECKING:
from generalresearch.incite.base import GRLDatasets
@@ -56,12 +52,12 @@ unsupported_mock_types = {
}
-def combo_object():
+def combo_object() -> Generator[str, None, None]:
for x in iter_product(
df_collections,
["15min", "45min", "1H"],
):
- yield x
+ yield from x
class TestDFCollectionItemBase:
@@ -199,7 +195,7 @@ class TestDFCollectionItemMethod:
client_no_amm,
incite_item_factory,
delete_df_collection,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
assert 1 + 1 == 2
@@ -768,7 +764,7 @@ class TestDFCollectionItemFunctionalTest:
product: Product,
incite_item_factory,
delete_df_collection,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
from generalresearch.models.thl.user import User
@@ -818,7 +814,7 @@ class TestDFCollectionItemFunctionalTest:
df_collection_data_type,
incite_item_factory,
delete_df_collection,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
"""A functional test to write some Parquet files for the
DFCollection and then confirm that the files get written
@@ -866,7 +862,7 @@ class TestDFCollectionItemFunctionalTest:
df_collection_data_type,
incite_item_factory,
delete_df_collection,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
from generalresearch.models.thl.user import User
@@ -919,7 +915,7 @@ class TestDFCollectionItemFunctionalTest:
product: Product,
offset: str,
duration: timedelta,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
"""Don't allow creating an archive for data that will likely be
overwritten or updated
@@ -960,7 +956,7 @@ class TestDFCollectionItemFunctionalTest:
user: User,
offset: str,
duration: timedelta,
- mnt_filepath: "GRLDatasets",
+ mnt_filepath: GRLDatasets,
):
delete_df_collection(coll=df_collection)
diff --git a/tests/incite/collections/test_df_collection_thl_marketplaces.py b/tests/incite/collections/test_df_collection_thl_marketplaces.py
index 8ce8acc..981f62e 100644
--- a/tests/incite/collections/test_df_collection_thl_marketplaces.py
+++ b/tests/incite/collections/test_df_collection_thl_marketplaces.py
@@ -28,7 +28,7 @@ def combo_object():
],
["5min", "6H", "30D"],
):
- yield x
+ yield from x
@pytest.mark.parametrize("df_coll, offset", combo_object())
diff --git a/tests/incite/collections/test_df_collection_thl_web.py b/tests/incite/collections/test_df_collection_thl_web.py
index c64dac8..b09d44c 100644
--- a/tests/incite/collections/test_df_collection_thl_web.py
+++ b/tests/incite/collections/test_df_collection_thl_web.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+from collections.abc import Generator
from datetime import datetime
from itertools import product
from typing import TYPE_CHECKING
@@ -11,9 +14,13 @@ from generalresearch.incite.collections import DFCollection, DFCollectionType
if TYPE_CHECKING:
from generalresearch.incite.base import GRLDatasets
+ from generalresearch.incite.collections import (
+ DFCollectionItem,
+ DFCollectionType,
+ )
-def combo_object():
+def combo_object() -> Generator[tuple, None, None]:
for x in product(
[
DFCollectionType.USER,
@@ -25,7 +32,7 @@ def combo_object():
],
["30min", "1H"],
):
- yield x
+ yield from x
@pytest.mark.parametrize(
@@ -33,7 +40,9 @@ def combo_object():
)
class TestDFCollection_thl_web:
- def test_init(self, df_collection_data_type, offset: str, df_collection):
+ def test_init(
+ self, df_collection_data_type: DFCollectionType, offset: str, df_collection
+ ):
assert isinstance(df_collection_data_type, DFCollectionType)
assert isinstance(df_collection, DFCollection)
@@ -43,12 +52,12 @@ class TestDFCollection_thl_web:
)
class TestDFCollection_thl_web_Properties:
- def test_items(self, df_collection_data_type, offset: str, df_collection):
+ def test_items(self, df_collection):
assert isinstance(df_collection.items, list)
for i in df_collection.items:
assert i._collection == df_collection
- def test__schema(self, df_collection_data_type, offset: str, df_collection):
+ def test__schema(self, df_collection):
assert isinstance(df_collection._schema, DataFrameSchema)
@@ -58,16 +67,16 @@ class TestDFCollection_thl_web_Properties:
class TestDFCollection_thl_web_BaseProperties:
@pytest.mark.skip
- def test__interval_range(self, df_collection_data_type, offset: str, df_collection):
+ def test__interval_range(self, df_collection):
pass
- def test_interval_start(self, df_collection_data_type, offset: str, df_collection):
+ def test_interval_start(self, df_collection):
assert isinstance(df_collection.interval_start, datetime)
- def test_interval_range(self, df_collection_data_type, offset: str, df_collection):
+ def test_interval_range(self, df_collection):
assert isinstance(df_collection.interval_range, list)
- def test_progress(self, df_collection_data_type, offset: str, df_collection):
+ def test_progress(self, df_collection):
assert isinstance(df_collection.progress, pd.DataFrame)
diff --git a/tests/models/custom_types/test_aware_datetime.py b/tests/models/custom_types/test_aware_datetime.py
index a23413c..14d1343 100644
--- a/tests/models/custom_types/test_aware_datetime.py
+++ b/tests/models/custom_types/test_aware_datetime.py
@@ -4,7 +4,7 @@ from typing import Optional
import pytest
import pytz
-from pydantic import BaseModel, ValidationError, Field
+from pydantic import BaseModel, Field, ValidationError
from generalresearch.models.custom_types import AwareDatetimeISO
diff --git a/tests/pytest.ini b/tests/pytest.ini
index d280de0..1a5c089 100644
--- a/tests/pytest.ini
+++ b/tests/pytest.ini
@@ -1,2 +1 @@
-[pytest]
-asyncio_mode = auto \ No newline at end of file
+[pytest] \ No newline at end of file
diff --git a/tests/test_postgres.py b/tests/test_postgres.py
new file mode 100644
index 0000000..3b3ddd0
--- /dev/null
+++ b/tests/test_postgres.py
@@ -0,0 +1,68 @@
+import socket
+import subprocess
+from typing import Callable
+
+from pydantic import PostgresDsn
+
+from generalresearch.models.custom_types import InternalHostname, PostgresDict
+from generalresearch.pg_helper import PostgresConfig
+
+
+def is_port_open(host: InternalHostname, port: int = 5432, timeout: int = 3):
+ try:
+ with socket.create_connection((host, port), timeout=timeout):
+ return True
+ except (socket.timeout, ConnectionRefusedError, OSError):
+ return False
+
+
+def can_ping(host: InternalHostname):
+ return (
+ subprocess.call(
+ ["ping", "-c", "1", str(host)],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+ == 0
+ )
+
+
+class TestPostgresDSN:
+
+ def test_ping(self, postgres_instance_host: InternalHostname):
+ assert can_ping(host=postgres_instance_host)
+
+ def test_port(self, postgres_instance_host: InternalHostname):
+ assert is_port_open(host=postgres_instance_host)
+
+ def test_conn(self, postgres_instance: PostgresDsn):
+ config = PostgresConfig(
+ dsn=postgres_instance,
+ connect_timeout=1,
+ statement_timeout=1,
+ )
+ res = config.execute_sql_query(query="SELECT 1;")
+ assert len(res) == 1
+
+
+class TestPostgresDjangoCreation:
+
+ def test_ping(self, postgres_instance_dict: PostgresDict):
+ assert can_ping(host=postgres_instance_dict["host"])
+
+ def test_django_creation(
+ self,
+ django_db_factory: Callable[..., None],
+ ):
+
+ dsn = django_db_factory()
+ assert isinstance(dsn, PostgresDsn)
+
+ def test_django_tables(self, thl_web_rw: PostgresConfig):
+ res = thl_web_rw.execute_sql_query(query="""
+ SELECT COUNT(*)
+ FROM information_schema.tables
+ WHERE table_schema = 'public';
+ """)
+ assert len(res) == 1
+ assert res[0]["count"] == 56