Got a functional status and account model and wrote a test for validating the status (but still requires an active vagrant instance which it does not create)
This commit is contained in:
parent
b156c57ff1
commit
d832e3a93e
14 changed files with 315 additions and 4 deletions
|
@ -1 +1,2 @@
|
|||
from diyalgo.config import Config
|
||||
from diyalgo.client.init import log_in
|
|
@ -5,7 +5,18 @@ from pydantic import BaseSettings, AnyHttpUrl, EmailStr
|
|||
class Config(BaseSettings):
|
||||
MASTO_URL:AnyHttpUrl
|
||||
MASTO_TOKEN: Optional[str] = None
|
||||
LOGDIR:Path = Path().home() / '.mastotools'
|
||||
LOGDIR:Path = Path().home() / '.diyalgo'
|
||||
DB:Optional[Path] = Path().home() / '.diyalgo' / 'diyalgo.db'
|
||||
"""
|
||||
Optional, if set to ``None`` , use the in-memory sqlite DB
|
||||
"""
|
||||
|
||||
@property
|
||||
def sqlite_path(self) -> str:
|
||||
if self.DB is None:
|
||||
return 'sqlite://'
|
||||
else:
|
||||
return f'sqlite:///{str(self.DB.resolve())}'
|
||||
|
||||
class Config:
|
||||
env_file = '.env'
|
||||
|
|
31
diyalgo/db.py
Normal file
31
diyalgo/db.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""
|
||||
Initialization routines for the database
|
||||
"""
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
# This should also import all the public models
|
||||
# so the engine is ready for them
|
||||
# https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/
|
||||
from diyalgo import models, Config
|
||||
|
||||
from sqlmodel import SQLModel, create_engine
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.future.engine import Engine
|
||||
|
||||
def init_db_engine(config:Optional[Config] = None) -> 'Engine':
|
||||
if config is None:
|
||||
config = Config()
|
||||
|
||||
engine = create_engine(config.sqlite_path)
|
||||
return engine
|
||||
|
||||
|
||||
def create_tables(
|
||||
engine: Optional['Engine'] = None,
|
||||
config:Optional[Config] = None
|
||||
):
|
||||
if engine is None:
|
||||
engine = init_db_engine(config=config)
|
||||
|
||||
SQLModel.metadata.create_all(engine)
|
|
@ -19,7 +19,7 @@ class Account(SQLModel, table=True):
|
|||
avatar_static: str
|
||||
bot: bool
|
||||
# created_at:datetime
|
||||
discoverable:bool
|
||||
discoverable:Optional[bool] = None
|
||||
display_name:str
|
||||
emojis: List['CustomEmoji'] = Relationship(back_populates='accounts', link_model=EmojiAccountLink)
|
||||
# fields: List["AccountField"] = Relationship(back_populates='account')
|
||||
|
|
|
@ -35,7 +35,7 @@ class Status(SQLModel, table=True):
|
|||
emojis: List['CustomEmoji'] = Relationship(back_populates='statuses', link_model=EmojiStatusLink)
|
||||
favourited: Optional[bool] = None
|
||||
favourites_count: int
|
||||
filtered: List[str] = Field(default_factory=list)
|
||||
#filtered: List[str] = Field(default_factory=list)
|
||||
in_reply_to_id: Optional[int] = None
|
||||
in_reply_to_account_id: Optional[int] = None
|
||||
language: Optional[str] = None
|
||||
|
@ -60,6 +60,16 @@ class Status(SQLModel, table=True):
|
|||
def soup(self) -> BeautifulSoup:
|
||||
return BeautifulSoup(self.content)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
from diyalgo.models import Account, MediaAttachment, Tag, CustomEmoji, Poll
|
||||
#kwargs['account'] = Account(**kwargs['account'])
|
||||
kwargs['account_id'] = kwargs['account']['id']
|
||||
del kwargs['account']
|
||||
kwargs['emojis'] = [CustomEmoji(**e) for e in kwargs['emojis']]
|
||||
kwargs['media_attachments'] = [MediaAttachment(**a) for a in kwargs['media_attachments']]
|
||||
kwargs['mentions'] = [Mention(**m) for m in kwargs['mentions']]
|
||||
kwargs['tags'] = [Tag(**t) for t in kwargs['tags']]
|
||||
super(Status, self).__init__(*args, **kwargs)
|
||||
|
||||
class Config:
|
||||
extra='ignore'
|
||||
|
|
124
poetry.lock
generated
124
poetry.lock
generated
|
@ -1,5 +1,24 @@
|
|||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
|
||||
{file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
|
||||
dev = ["attrs[docs,tests]"]
|
||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
|
||||
tests = ["attrs[tests-no-zope]", "zope.interface"]
|
||||
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.11.1"
|
||||
|
@ -61,6 +80,18 @@ files = [
|
|||
[package.extras]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.1.1"
|
||||
|
@ -73,6 +104,21 @@ files = [
|
|||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.1.0"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
|
||||
{file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "2.0.1"
|
||||
|
@ -159,6 +205,18 @@ files = [
|
|||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "4.9.2"
|
||||
|
@ -277,6 +335,34 @@ blurhash = ["blurhash (>=1.1.4)"]
|
|||
test = ["blurhash (>=1.1.4)", "cryptography (>=1.6.0)", "http-ece (>=1.0.5)", "pytest", "pytest-cov", "pytest-mock", "pytest-runner", "pytest-vcr", "pytz", "requests-mock", "vcrpy"]
|
||||
webpush = ["cryptography (>=1.6.0)", "http-ece (>=1.0.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "22.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"},
|
||||
{file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.4"
|
||||
|
@ -330,6 +416,30 @@ typing-extensions = ">=4.2.0"
|
|||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.2.0"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
|
||||
{file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
|
@ -525,6 +635,18 @@ pydantic = ">=1.8.2,<2.0.0"
|
|||
SQLAlchemy = ">=1.4.17,<=1.4.41"
|
||||
sqlalchemy2-stubs = "*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.4.0"
|
||||
|
@ -557,4 +679,4 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "bcb1da145e3fb10a5e98a37ae6f4e9933e32b3152fe17799e964187294f7a890"
|
||||
content-hash = "3504f840231279fd4e2ef50e2990824d644e1b9e0720eaf0059887d4de26baf6"
|
||||
|
|
|
@ -18,6 +18,11 @@ beautifulsoup4 = "^4.11.1"
|
|||
lxml = "^4.9.2"
|
||||
python-dotenv = "^0.21.0"
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^7.2.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/fixtures/__init__.py
vendored
Normal file
0
tests/fixtures/__init__.py
vendored
Normal file
24
tests/fixtures/client.py
vendored
Normal file
24
tests/fixtures/client.py
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
import pytest
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mastodon import Mastodon
|
||||
from diyalgo import Config
|
||||
|
||||
from diyalgo.client.init import log_in
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def client_fixture(config:Optional['Config'] = None) -> 'Mastodon':
|
||||
"""
|
||||
A logged in client with the default credentials (in an .env file atm)
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
client = log_in(config)
|
||||
|
||||
yield client
|
||||
|
||||
client.session.close()
|
||||
|
9
tests/fixtures/config.py
vendored
Normal file
9
tests/fixtures/config.py
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import pytest
|
||||
|
||||
from diyalgo import Config
|
||||
|
||||
@pytest.fixture()
|
||||
def config_fixture() -> Config:
|
||||
return Config(
|
||||
DB=None # use in_RAM database
|
||||
)
|
26
tests/fixtures/db.py
vendored
Normal file
26
tests/fixtures/db.py
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
import pytest
|
||||
|
||||
from sqlalchemy.future.engine import Engine
|
||||
from sqlmodel import SQLModel, Session
|
||||
|
||||
from .config import config_fixture
|
||||
|
||||
from diyalgo import models
|
||||
from diyalgo.db import init_db_engine, create_tables
|
||||
|
||||
@pytest.fixture()
|
||||
def engine_fixture(config_fixture) -> Engine:
|
||||
"""Create and engine and then create tables"""
|
||||
engine = init_db_engine(config_fixture)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
yield engine
|
||||
|
||||
engine.dispose()
|
||||
|
||||
@pytest.fixture()
|
||||
def session_fixture(engine_fixture) -> Session:
|
||||
return Session(engine_fixture)
|
||||
|
||||
|
||||
|
10
tests/test_client.py
Normal file
10
tests/test_client.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import pytest
|
||||
|
||||
from .fixtures.client import client_fixture as client
|
||||
|
||||
def test_log_in(client):
|
||||
|
||||
assert client.access_token is not None
|
||||
assert isinstance(client._Mastodon__get_logged_in_id(), int)
|
||||
|
||||
|
62
tests/test_model.py
Normal file
62
tests/test_model.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import pytest
|
||||
import pdb
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from .fixtures.config import config_fixture
|
||||
from .fixtures.client import client_fixture as client_fixture
|
||||
from .fixtures.db import session_fixture, engine_fixture
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
from diyalgo.models import Status
|
||||
|
||||
@pytest.mark.parametrize('status_str', ['My test status'])
|
||||
def test_status_text(client_fixture, session_fixture, status_str):
|
||||
posted = client_fixture.status_post(status_str)
|
||||
|
||||
status = Status(**posted)
|
||||
with session_fixture as session:
|
||||
session.add(status)
|
||||
session.commit()
|
||||
|
||||
# compare to committed status
|
||||
status = session.exec(select(Status).where(Status.id == posted['id'])).first()
|
||||
# model uses account_id instead of full account object
|
||||
posted['account_id'] = posted['account']['id']
|
||||
for k, v in status.dict().items():
|
||||
# text is a special case that's only relevant when the status has been deleted:
|
||||
# https://docs.joinmastodon.org/entities/Status/#text
|
||||
if k in ('text', 'account'):
|
||||
continue
|
||||
|
||||
# we gain utc timezone for datetimes
|
||||
if isinstance(v, datetime):
|
||||
assert v == posted[k].replace(tzinfo=None)
|
||||
else:
|
||||
assert v == posted[k]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# from diyalgo.client.init import log_in
|
||||
# from diyalgo import models
|
||||
# from diyalgo.db import init_db_engine, create_tables
|
||||
# from sqlmodel import Session
|
||||
#
|
||||
# client = log_in()
|
||||
# posted = client.status_post('hey my status')
|
||||
# status = models.Status(**posted)
|
||||
#
|
||||
# engine = init_db_engine()
|
||||
# create_tables(engine)
|
||||
# session = Session(engine)
|
||||
#
|
||||
# session.add(status)
|
||||
# session.commit()
|
||||
#
|
||||
# posted2 = client.status_post('hey my status again')
|
||||
# status2 = models.Status(**posted2)
|
||||
# session.add(status2)
|
||||
# session.commit()
|
Loading…
Reference in a new issue