Initial commit
This commit is contained in:
parent
1ba1125c45
commit
a301e345f4
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.mypy_cache/
|
||||
*.pyc
|
||||
*.swp
|
||||
tags
|
||||
Session.vim
|
||||
.direnv
|
||||
.envrc
|
||||
0
README.rst
Normal file
0
README.rst
Normal file
85
alembic.ini
Normal file
85
alembic.ini
Normal file
@ -0,0 +1,85 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = migrations
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# timezone to use when rendering the date
|
||||
# within the migration file as well as the filename.
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; this defaults
|
||||
# to migrations/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path
|
||||
# version_locations = %(here)s/bar %(here)s/bat migrations/versions
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks=black
|
||||
# black.type=console_scripts
|
||||
# black.entrypoint=black
|
||||
# black.options=-l 79
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = '0.1.0'
|
||||
21
app/config.py
Normal file
21
app/config.py
Normal file
@ -0,0 +1,21 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
class Config(object):
|
||||
ACCESS_TOKEN = '/home/kae/git/urma/app/urma_usercred.secret'
|
||||
# KAEID = 109568725613662482
|
||||
# DEBUG_FUNCTIONS: List[Optional[str]] = []
|
||||
# DEBUG_MODULES: List[Optional[str]] = ['dbconfig']
|
||||
DISPLAY_SQL = True
|
||||
# ERRORS_FROM = ['noreply@midnighthax.com']
|
||||
# ERRORS_TO = ['kae@midnighthax.com']
|
||||
LOG_LEVEL_STDERR = logging.ERROR
|
||||
LOG_LEVEL_SYSLOG = logging.DEBUG
|
||||
LOG_NAME = "urma"
|
||||
# MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
# MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25)
|
||||
# MAIL_SERVER = os.environ.get('MAIL_SERVER') or
|
||||
# "woodlands.midnighthax.com"
|
||||
# MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
# MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
|
||||
42
app/dbconfig.py
Normal file
42
app/dbconfig.py
Normal file
@ -0,0 +1,42 @@
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
from config import Config
|
||||
from contextlib import contextmanager
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import (sessionmaker, scoped_session)
|
||||
from typing import Generator
|
||||
|
||||
from log import log
|
||||
|
||||
MYSQL_CONNECT = os.environ.get('URMA_DB')
|
||||
if MYSQL_CONNECT is None:
|
||||
raise ValueError("MYSQL_CONNECT is undefined")
|
||||
else:
|
||||
dbname = MYSQL_CONNECT.split('/')[-1]
|
||||
log.debug(f"Database: {dbname}")
|
||||
|
||||
engine = create_engine(
|
||||
MYSQL_CONNECT,
|
||||
encoding='utf-8',
|
||||
echo=Config.DISPLAY_SQL,
|
||||
pool_pre_ping=True,
|
||||
future=True
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def Session() -> Generator[scoped_session, None, None]:
|
||||
frame = inspect.stack()[2]
|
||||
file = frame.filename
|
||||
function = frame.function
|
||||
lineno = frame.lineno
|
||||
Session = scoped_session(sessionmaker(bind=engine, future=True))
|
||||
log.debug(
|
||||
f"Session acquired, {file=}, {function=}, "
|
||||
f"function{lineno=}, {Session=}"
|
||||
)
|
||||
yield Session
|
||||
log.debug(" Session released")
|
||||
Session.commit()
|
||||
Session.close()
|
||||
54
app/helpers.py
Normal file
54
app/helpers.py
Normal file
@ -0,0 +1,54 @@
|
||||
import os
|
||||
import smtplib
|
||||
import ssl
|
||||
|
||||
from email.message import EmailMessage
|
||||
|
||||
from config import Config
|
||||
from log import log
|
||||
|
||||
|
||||
def ask_yes_no(title: str, question: str) -> bool:
|
||||
"""Ask question; return True for yes, False for no"""
|
||||
|
||||
button_reply = QMessageBox.question(None, title, question)
|
||||
|
||||
return button_reply == QMessageBox.Yes
|
||||
|
||||
|
||||
def send_mail(to_addr, from_addr, subj, body):
|
||||
# From https://docs.python.org/3/library/email.examples.html
|
||||
|
||||
# Create a text/plain message
|
||||
msg = EmailMessage()
|
||||
msg.set_content(body)
|
||||
|
||||
msg['Subject'] = subj
|
||||
msg['From'] = from_addr
|
||||
msg['To'] = to_addr
|
||||
|
||||
# Send the message via SMTP server.
|
||||
context = ssl.create_default_context()
|
||||
try:
|
||||
s = smtplib.SMTP(host=Config.MAIL_SERVER, port=Config.MAIL_PORT)
|
||||
if Config.MAIL_USE_TLS:
|
||||
s.starttls(context=context)
|
||||
if Config.MAIL_USERNAME and Config.MAIL_PASSWORD:
|
||||
s.login(Config.MAIL_USERNAME, Config.MAIL_PASSWORD)
|
||||
s.send_message(msg)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
s.quit()
|
||||
|
||||
|
||||
def show_OK(title: str, msg: str) -> None:
|
||||
"""Display a message to user"""
|
||||
|
||||
QMessageBox.information(None, title, msg, buttons=QMessageBox.Ok)
|
||||
|
||||
|
||||
def show_warning(title: str, msg: str) -> None:
|
||||
"""Display a warning to user"""
|
||||
|
||||
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
|
||||
85
app/log.py
Normal file
85
app/log.py
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import stackprinter # type: ignore
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from config import Config
|
||||
|
||||
|
||||
class LevelTagFilter(logging.Filter):
|
||||
"""Add leveltag"""
|
||||
|
||||
def filter(self, record: logging.LogRecord):
|
||||
# Extract the first character of the level name
|
||||
record.leveltag = record.levelname[0]
|
||||
|
||||
# We never actually filter messages out, just abuse filtering to add an
|
||||
# extra field to the LogRecord
|
||||
return True
|
||||
|
||||
|
||||
class DebugStdoutFilter(logging.Filter):
|
||||
"""Filter debug messages sent to stdout"""
|
||||
|
||||
def filter(self, record: logging.LogRecord):
|
||||
# Exceptions are logged at ERROR level
|
||||
if record.levelno in [logging.DEBUG, logging.ERROR]:
|
||||
return True
|
||||
if record.module in Config.DEBUG_MODULES:
|
||||
return True
|
||||
if record.funcName in Config.DEBUG_FUNCTIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
log = logging.getLogger(Config.LOG_NAME)
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
# stderr
|
||||
stderr = logging.StreamHandler()
|
||||
stderr.setLevel(Config.LOG_LEVEL_STDERR)
|
||||
|
||||
# syslog
|
||||
syslog = logging.handlers.SysLogHandler(address='/dev/log')
|
||||
syslog.setLevel(Config.LOG_LEVEL_SYSLOG)
|
||||
|
||||
# Filter
|
||||
local_filter = LevelTagFilter()
|
||||
debug_filter = DebugStdoutFilter()
|
||||
|
||||
syslog.addFilter(local_filter)
|
||||
|
||||
stderr.addFilter(local_filter)
|
||||
stderr.addFilter(debug_filter)
|
||||
|
||||
stderr_fmt = logging.Formatter('[%(asctime)s] %(leveltag)s: %(message)s',
|
||||
datefmt='%H:%M:%S')
|
||||
syslog_fmt = logging.Formatter(
|
||||
'[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
|
||||
)
|
||||
stderr.setFormatter(stderr_fmt)
|
||||
syslog.setFormatter(syslog_fmt)
|
||||
|
||||
log.addHandler(stderr)
|
||||
log.addHandler(syslog)
|
||||
|
||||
|
||||
def log_uncaught_exceptions(_ex_cls, ex, tb):
|
||||
|
||||
from helpers import send_mail
|
||||
|
||||
print("\033[1;31;47m")
|
||||
logging.critical(''.join(traceback.format_tb(tb)))
|
||||
print("\033[1;37;40m")
|
||||
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
|
||||
if os.environ["MM_ENV"] != "DEVELOPMENT":
|
||||
msg = stackprinter.format(ex)
|
||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||
"Exception from musicmuster", msg)
|
||||
|
||||
|
||||
sys.excepthook = log_uncaught_exceptions
|
||||
121
app/models.py
Normal file
121
app/models.py
Normal file
@ -0,0 +1,121 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os.path
|
||||
|
||||
from dbconfig import Session, scoped_session
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
)
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
from sqlalchemy.orm import (
|
||||
declarative_base,
|
||||
relationship,
|
||||
)
|
||||
from config import Config
|
||||
from log import log
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Database classes
|
||||
class Accounts(Base):
|
||||
__tablename__ = 'accounts'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
account_id = Column(Integer, index=True, nullable=False)
|
||||
username = Column(String(256), index=True, nullable=False)
|
||||
acct = Column(String(256), index=False, nullable=False)
|
||||
display_name = Column(String(256), index=False, nullable=False)
|
||||
bot = Column(Boolean, index=False, nullable=False, default=False)
|
||||
url = Column(String(256), index=False)
|
||||
followed = Column(Boolean, index=False, nullable=False, default=False)
|
||||
posts = relationship("Posts", back_populates="account")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Accounts(id={self.id}, url={self.content[:60]}, "
|
||||
f"followed={self.followed}>"
|
||||
)
|
||||
|
||||
|
||||
class Attachments(Base):
|
||||
__tablename__ = 'attachments'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
media_id = Column(Integer, index=True, nullable=False)
|
||||
media_type = Column(String(256), index=False)
|
||||
url = Column(String(256), index=False)
|
||||
preview_url = Column(String(256), index=False)
|
||||
description = Column(String(2048), index=False)
|
||||
posts = relationship("Posts", back_populates="media_attachments")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Attachments(id={self.id}, url={self.url}, "
|
||||
f"content={self.content[:60]}, followed={self.followed}>"
|
||||
)
|
||||
|
||||
|
||||
class Hashtags(Base):
|
||||
__tablename__ = 'hashtags'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(256), index=True, nullable=False)
|
||||
url = Column(String(256), index=False)
|
||||
posts = relationship("Posts", secondary="post_tags", backref="hashtags")
|
||||
followed = Column(Boolean, index=False, nullable=False, default=False)
|
||||
posttags = relationship("PostTags", back_populates="hashtag")
|
||||
posts = association_proxy("posttags", "post")
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Hashtags(id={self.id}, name={self.name}, "
|
||||
f"url={self.url}>, followed={self.followed}>"
|
||||
)
|
||||
|
||||
|
||||
class Posts(Base):
|
||||
__tablename__ = 'posts'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
post_id = Column(Integer, index=True, nullable=False)
|
||||
created_at = Column(DateTime, index=True, default=None)
|
||||
uri = Column(String(256), index=False)
|
||||
url = Column(String(256), index=False)
|
||||
content = Column(String(2048), index=False, default="")
|
||||
account_id = Column(Integer, ForeignKey('accounts.id'), nullable=True)
|
||||
account = relationship("Accounts", back_populates="posts")
|
||||
|
||||
parent_id = Column(Integer, ForeignKey("posts.id"))
|
||||
reblog = relationship("Posts")
|
||||
|
||||
media_attachments_id = Column(Integer, ForeignKey('attachments.id'),
|
||||
nullable=True)
|
||||
media_attachments = relationship("Attachments", back_populates="posts")
|
||||
|
||||
posttags = relationship("PostTags", back_populates="post")
|
||||
rating = Column(Integer, index=True, default=None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Posts(id={self.id}, content={self.content[:60]}>"
|
||||
|
||||
|
||||
class PostTags(Base):
|
||||
__tablename__ = 'post_tags'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
post_id = Column(Integer, ForeignKey('posts.id'), nullable=False)
|
||||
post = relationship(Posts, back_populates="posttags")
|
||||
|
||||
hashtag_id = Column(Integer, ForeignKey('hashtags.id'), nullable=False)
|
||||
hashtag = relationship("Hashtags", back_populates="posttags")
|
||||
58
app/urma.py
Executable file
58
app/urma.py
Executable file
@ -0,0 +1,58 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import pickle
|
||||
|
||||
from config import Config
|
||||
from dbconfig import engine
|
||||
from log import log
|
||||
from mastodon import Mastodon
|
||||
from models import (
|
||||
Accounts,
|
||||
Attachments,
|
||||
Base,
|
||||
Hashtags,
|
||||
Posts,
|
||||
)
|
||||
|
||||
TESTDATA = "/home/kae/git/urma/hometl.pickle"
|
||||
|
||||
# Mastodon.create_app(
|
||||
# 'urma',
|
||||
# api_base_url='mastodon.org.uk',
|
||||
# to_file='urma_clientcred.secret'
|
||||
# )
|
||||
|
||||
# API_BASE_URL = 'mastodon.org.uk'
|
||||
|
||||
# mastodon = Mastodon(client_id = 'urma_clientcred.secret',)
|
||||
# mastodon.log_in('kae@midnighthax.com', '^ZUaiC8P6vLV49',
|
||||
# to_file='urma_usercred.secret')
|
||||
# hometl = Mastodon.timeline()
|
||||
# hometl = mastodon.timeline()
|
||||
# hometl
|
||||
# len(hometl)
|
||||
# hometl0=hometl[0]
|
||||
# hometl0
|
||||
# history
|
||||
# mastodon.me()
|
||||
# following=mastodon.account_following(kaeid)
|
||||
# len(following)
|
||||
# following[0]
|
||||
# following[39]
|
||||
# following._pagination_next
|
||||
# following._pagination_prev
|
||||
# history
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
# mastodon = Mastodon(access_token=Config.ACCESS_TOKEN)
|
||||
|
||||
# Data for development
|
||||
with open(TESTDATA, "rb") as inp:
|
||||
hometl = pickle.load(inp)
|
||||
|
||||
post = Posts()
|
||||
import ipdb; ipdb.set_trace()
|
||||
|
||||
# Parse timeline
|
||||
# for post in hometl:
|
||||
# post = Posts()
|
||||
4
app/urma_clientcred.secret
Normal file
4
app/urma_clientcred.secret
Normal file
@ -0,0 +1,4 @@
|
||||
0x02PA7g-THgRAXnB4bXJn5sml9aLybhOsJ-G0-_lgA
|
||||
t5XSntLUVf8lrNLweZ5bC8KD6YoccD_G6-93ecgWEgg
|
||||
https://mastodon.org.uk
|
||||
kaemasto
|
||||
4
app/urma_usercred.secret
Normal file
4
app/urma_usercred.secret
Normal file
@ -0,0 +1,4 @@
|
||||
XAODnyOFfYEJ0OLXWkAM5QKkFknoyD8zjLaLPxmoECk
|
||||
https://mastodon.org.uk
|
||||
0x02PA7g-THgRAXnB4bXJn5sml9aLybhOsJ-G0-_lgA
|
||||
t5XSntLUVf8lrNLweZ5bC8KD6YoccD_G6-93ecgWEgg
|
||||
BIN
hometl.pickle
Normal file
BIN
hometl.pickle
Normal file
Binary file not shown.
1
migrations/README
Normal file
1
migrations/README
Normal file
@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
83
migrations/env.py
Normal file
83
migrations/env.py
Normal file
@ -0,0 +1,83 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
from alembic import context
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
path = os.path.dirname(os.path.dirname(__file__))
|
||||
sys.path.insert(0, path)
|
||||
sys.path.insert(0, os.path.join(path, "app"))
|
||||
from app.models import Base
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
url = os.environ.get('URMA_DB')
|
||||
print()
|
||||
print(f"Alembic: {url=}")
|
||||
print()
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = create_engine(url)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
||||
@ -0,0 +1,34 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 0d4c8f368e00
|
||||
Revises: 5281c8c8059d
|
||||
Create Date: 2023-01-02 14:28:57.905087
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0d4c8f368e00'
|
||||
down_revision = '5281c8c8059d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('post_tags', sa.Column('post_id', sa.Integer(), nullable=False))
|
||||
op.drop_constraint('post_tags_ibfk_2', 'post_tags', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'post_tags', 'posts', ['post_id'], ['id'])
|
||||
op.drop_column('post_tags', 'posts_id')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('post_tags', sa.Column('posts_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'post_tags', type_='foreignkey')
|
||||
op.create_foreign_key('post_tags_ibfk_2', 'post_tags', 'posts', ['posts_id'], ['id'])
|
||||
op.drop_column('post_tags', 'post_id')
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 1132a20d56cb
|
||||
Revises: 4a6731c9e71b
|
||||
Create Date: 2023-01-02 13:13:07.084963
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1132a20d56cb'
|
||||
down_revision = '4a6731c9e71b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 28448b5f994f
|
||||
Revises: 1132a20d56cb
|
||||
Create Date: 2023-01-02 13:15:19.817927
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '28448b5f994f'
|
||||
down_revision = '1132a20d56cb'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('attachments', 'followed')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('attachments', sa.Column('followed', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 354c25d6adac
|
||||
Revises: 0d4c8f368e00
|
||||
Create Date: 2023-01-02 14:37:16.192979
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '354c25d6adac'
|
||||
down_revision = '0d4c8f368e00'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Initial Alembic configuration
|
||||
|
||||
Revision ID: 40a36c2e0e8d
|
||||
Revises:
|
||||
Create Date: 2023-01-02 08:15:56.863042
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '40a36c2e0e8d'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,34 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 4a6731c9e71b
|
||||
Revises: 563253042f7e
|
||||
Create Date: 2023-01-02 13:12:21.344294
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4a6731c9e71b'
|
||||
down_revision = '563253042f7e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('posts', sa.Column('parent_id', sa.Integer(), nullable=True))
|
||||
op.drop_constraint('posts_ibfk_2', 'posts', type_='foreignkey')
|
||||
op.create_foreign_key(None, 'posts', 'posts', ['parent_id'], ['id'])
|
||||
op.drop_column('posts', 'reblog_id')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('posts', sa.Column('reblog_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'posts', type_='foreignkey')
|
||||
op.create_foreign_key('posts_ibfk_2', 'posts', 'posts', ['reblog_id'], ['id'])
|
||||
op.drop_column('posts', 'parent_id')
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 5281c8c8059d
|
||||
Revises: 28448b5f994f
|
||||
Create Date: 2023-01-02 13:16:37.480999
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5281c8c8059d'
|
||||
down_revision = '28448b5f994f'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 563253042f7e
|
||||
Revises: 7c67a545533e
|
||||
Create Date: 2023-01-02 13:00:44.136697
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '563253042f7e'
|
||||
down_revision = '7c67a545533e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,28 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 7672338beb90
|
||||
Revises: 354c25d6adac
|
||||
Create Date: 2023-01-02 14:39:26.283627
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7672338beb90'
|
||||
down_revision = '354c25d6adac'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
@ -0,0 +1,32 @@
|
||||
"""Fixing table relationships
|
||||
|
||||
Revision ID: 7c67a545533e
|
||||
Revises: 40a36c2e0e8d
|
||||
Create Date: 2023-01-02 12:56:05.031475
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7c67a545533e'
|
||||
down_revision = '40a36c2e0e8d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('accounts', sa.Column('followed', sa.Boolean(), nullable=False))
|
||||
op.add_column('attachments', sa.Column('followed', sa.Boolean(), nullable=False))
|
||||
op.add_column('hashtags', sa.Column('followed', sa.Boolean(), nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('hashtags', 'followed')
|
||||
op.drop_column('attachments', 'followed')
|
||||
op.drop_column('accounts', 'followed')
|
||||
# ### end Alembic commands ###
|
||||
638
poetry.lock
generated
Normal file
638
poetry.lock
generated
Normal file
@ -0,0 +1,638 @@
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.9.1"
|
||||
description = "A database migration tool for SQLAlchemy."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
Mako = "*"
|
||||
SQLAlchemy = ">=1.3.0"
|
||||
|
||||
[package.extras]
|
||||
tz = ["python-dateutil"]
|
||||
|
||||
[[package]]
|
||||
name = "appnope"
|
||||
version = "0.1.3"
|
||||
description = "Disable App Nap on macOS >= 10.9"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.2.1"
|
||||
description = "Annotate AST trees with source code positions"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
test = ["astroid", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.1"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
|
||||
dev = ["attrs"]
|
||||
docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
|
||||
tests = ["attrs", "zope.interface"]
|
||||
tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"]
|
||||
tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "backcall"
|
||||
version = "0.2.0"
|
||||
description = "Specifications for callback functions passed in to an API"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "blurhash"
|
||||
version = "1.1.4"
|
||||
description = "Pure-Python implementation of the blurhash algorithm."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
test = ["pillow", "numpy", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.12.7"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
|
||||
[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"
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.1.1"
|
||||
description = "Decorators for Humans"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "1.2.0"
|
||||
description = "Get the currently executing AST node of a frame, and other information"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["asttokens", "pytest", "littleutils", "rich"]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "2.0.1"
|
||||
description = "Lightweight in-process concurrent programming"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "docutils (<0.18)"]
|
||||
test = ["objgraph", "psutil", "faulthandler"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "ipdb"
|
||||
version = "0.13.11"
|
||||
description = "IPython-enabled pdb"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
decorator = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\" or python_version >= \"3.11\""}
|
||||
ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\" and python_version < \"3.11\" or python_version >= \"3.11\""}
|
||||
tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.7.0"
|
||||
description = "IPython: Productive Interactive Computing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[package.dependencies]
|
||||
appnope = {version = "*", markers = "sys_platform == \"darwin\""}
|
||||
backcall = "*"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
decorator = "*"
|
||||
jedi = ">=0.16"
|
||||
matplotlib-inline = "*"
|
||||
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
|
||||
pickleshare = "*"
|
||||
prompt-toolkit = ">=3.0.11,<3.1.0"
|
||||
pygments = ">=2.4.0"
|
||||
stack-data = "*"
|
||||
traitlets = ">=5"
|
||||
|
||||
[package.extras]
|
||||
all = ["black", "ipykernel", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "docrepr", "matplotlib", "stack-data", "pytest (<7)", "typing-extensions", "pytest (<7.1)", "pytest-asyncio", "testpath", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.20)", "pandas", "trio"]
|
||||
black = ["black"]
|
||||
doc = ["ipykernel", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "docrepr", "matplotlib", "stack-data", "pytest (<7)", "typing-extensions", "pytest (<7.1)", "pytest-asyncio", "testpath"]
|
||||
kernel = ["ipykernel"]
|
||||
nbconvert = ["nbconvert"]
|
||||
nbformat = ["nbformat"]
|
||||
notebook = ["ipywidgets", "notebook"]
|
||||
parallel = ["ipyparallel"]
|
||||
qtconsole = ["qtconsole"]
|
||||
test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
|
||||
test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.20)", "pandas", "trio"]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.18.2"
|
||||
description = "An autocompletion tool for Python that can be used for text editors."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
parso = ">=0.8.0,<0.9.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx-rtd-theme (==0.4.3)", "sphinx (==1.8.5)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
|
||||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.2.4"
|
||||
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=0.9.2"
|
||||
|
||||
[package.extras]
|
||||
babel = ["babel"]
|
||||
lingua = ["lingua"]
|
||||
testing = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "mastodon.py"
|
||||
version = "1.8.0"
|
||||
description = "Python wrapper for the Mastodon API"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
blurhash = ">=1.1.4"
|
||||
decorator = ">=4.0.0"
|
||||
python-dateutil = "*"
|
||||
python-magic = "*"
|
||||
requests = ">=2.4.2"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
blurhash = ["blurhash (>=1.1.4)"]
|
||||
test = ["pytest", "pytest-runner", "pytest-cov", "vcrpy", "pytest-vcr", "pytest-mock", "requests-mock", "pytz", "http-ece (>=1.0.5)", "cryptography (>=1.6.0)", "blurhash (>=1.1.4)"]
|
||||
webpush = ["http-ece (>=1.0.5)", "cryptography (>=1.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.6"
|
||||
description = "Inline Matplotlib backend for Jupyter"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
traitlets = "*"
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "9.0.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "mysqlclient"
|
||||
version = "2.1.1"
|
||||
description = "Python interface to MySQL"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "22.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.3"
|
||||
description = "A Python Parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["docopt", "pytest (<6.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pexpect"
|
||||
version = "4.8.0"
|
||||
description = "Pexpect allows easy control of interactive console applications."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
ptyprocess = ">=0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pickleshare"
|
||||
version = "0.7.5"
|
||||
description = "Tiny 'shelve'-like database with concurrency support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "0.13.1"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.36"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
description = "Run a subprocess in a pseudo terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.2"
|
||||
description = "Safely evaluate AST nodes without side effects"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.14.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
plugins = ["importlib-metadata"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "5.4.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=17.4.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (==v0.761)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-magic"
|
||||
version = "0.4.27"
|
||||
description = "File type identification using libmagic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.28.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7, <4"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<3"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
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.*"
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "1.4.45"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (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\")"}
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["greenlet (!=0.4.17)", "aiomysql"]
|
||||
aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"]
|
||||
asyncio = ["greenlet (!=0.4.17)"]
|
||||
asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3,!=0.2.4)"]
|
||||
mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"]
|
||||
mssql = ["pyodbc"]
|
||||
mssql_pymssql = ["pymssql"]
|
||||
mssql_pyodbc = ["pyodbc"]
|
||||
mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"]
|
||||
mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"]
|
||||
mysql_connector = ["mysql-connector-python"]
|
||||
oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
|
||||
postgresql = ["psycopg2 (>=2.7)"]
|
||||
postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"]
|
||||
postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
|
||||
postgresql_psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql (<1)", "pymysql"]
|
||||
sqlcipher = ["sqlcipher3-binary"]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.2"
|
||||
description = "Extract data from python stack frames and tracebacks for informative displays"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
asttokens = ">=2.1.0"
|
||||
executing = ">=1.2.0"
|
||||
pure-eval = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"]
|
||||
|
||||
[[package]]
|
||||
name = "stackprinter"
|
||||
version = "0.2.10"
|
||||
description = "Debug-friendly stack traces, with variable values and semantic highlighting"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.4"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.8.0"
|
||||
description = "Traitlets Python configuration system"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||
test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.13"
|
||||
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.*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.5"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "56ce5fa8480bc5fc923ac1c8ae72ac995690fdfc1ba42cd62f9a8f4737919d8d"
|
||||
|
||||
[metadata.files]
|
||||
alembic = []
|
||||
appnope = []
|
||||
asttokens = []
|
||||
atomicwrites = []
|
||||
attrs = []
|
||||
backcall = [
|
||||
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
|
||||
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
|
||||
]
|
||||
blurhash = []
|
||||
certifi = []
|
||||
charset-normalizer = []
|
||||
colorama = []
|
||||
decorator = [
|
||||
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
|
||||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||
]
|
||||
executing = []
|
||||
greenlet = []
|
||||
idna = []
|
||||
ipdb = []
|
||||
ipython = []
|
||||
jedi = []
|
||||
mako = []
|
||||
markupsafe = []
|
||||
"mastodon.py" = []
|
||||
matplotlib-inline = []
|
||||
more-itertools = []
|
||||
mysqlclient = []
|
||||
packaging = []
|
||||
parso = [
|
||||
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
|
||||
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
|
||||
]
|
||||
pexpect = [
|
||||
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
|
||||
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
|
||||
]
|
||||
pickleshare = [
|
||||
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
|
||||
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
prompt-toolkit = []
|
||||
ptyprocess = [
|
||||
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
|
||||
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
|
||||
]
|
||||
pure-eval = [
|
||||
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
|
||||
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pygments = []
|
||||
pytest = [
|
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
python-magic = []
|
||||
requests = []
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
sqlalchemy = []
|
||||
stack-data = []
|
||||
stackprinter = []
|
||||
tomli = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
traitlets = []
|
||||
urllib3 = []
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
||||
25
pyproject.toml
Normal file
25
pyproject.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[tool.poetry]
|
||||
name = "urma"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Keith Edmunds <kae@midnighthax.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
"Mastodon.py" = "^1.8.0"
|
||||
stackprinter = "^0.2.10"
|
||||
SQLAlchemy = "^1.4.45"
|
||||
mysqlclient = "^2.1.1"
|
||||
alembic = "^1.9.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^5.2"
|
||||
ipdb = "^0.13.11"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/urma-e3I_sS5U-py3.9:/home/kae/git/urma/app"
|
||||
plugins = "sqlalchemy.ext.mypy.plugin"
|
||||
135
sample_reblog.txt
Normal file
135
sample_reblog.txt
Normal file
@ -0,0 +1,135 @@
|
||||
{'id': 109615165821993543,
|
||||
'created_at': datetime.datetime(2023, 1, 1, 17, 38, 12, tzinfo=tzutc()),
|
||||
'in_reply_to_id': None,
|
||||
'in_reply_to_account_id': None,
|
||||
'sensitive': False,
|
||||
'spoiler_text': '',
|
||||
'visibility': 'public',
|
||||
'language': None,
|
||||
'uri': 'https://ohai.social/users/SecularJeffrey/statuses/109615165735453622/activity',
|
||||
'url': None,
|
||||
'replies_count': 0,
|
||||
'reblogs_count': 0,
|
||||
'favourites_count': 0,
|
||||
'edited_at': None,
|
||||
'favourited': False,
|
||||
'reblogged': False,
|
||||
'muted': False,
|
||||
'bookmarked': False,
|
||||
'content': '',
|
||||
'filtered': [],
|
||||
'reblog': {'id': 109615047364584011,
|
||||
'created_at': datetime.datetime(2023, 1, 1, 17, 8, 3, tzinfo=tzutc()),
|
||||
'in_reply_to_id': None,
|
||||
'in_reply_to_account_id': None,
|
||||
'sensitive': False,
|
||||
'spoiler_text': '',
|
||||
'visibility': 'public',
|
||||
'language': 'en',
|
||||
'uri': 'https://mastodon.scot/users/UndisScot/statuses/109615047228306787',
|
||||
'url': 'https://mastodon.scot/@UndisScot/109615047228306787',
|
||||
'replies_count': 1,
|
||||
'reblogs_count': 1,
|
||||
'favourites_count': 0,
|
||||
'edited_at': None,
|
||||
'favourited': False,
|
||||
'reblogged': False,
|
||||
'muted': False,
|
||||
'bookmarked': False,
|
||||
'content': '<p>A VIRTUAL TOUR OF SCOTLAND</p><p>We travel 22 miles north-west by road to Tobermory, the island capital of the Isle of Mull with a population just over 1,000 people. It faces south-east into the Sound of Mull, protected by Calve Island. Main Street hugs the harbour, with additional development on the hillside behind. More pics and info: <a href="https://www.undiscoveredscotland.co.uk/mull/tobermory/index.html" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://www.</span><span class="ellipsis">undiscoveredscotland.co.uk/mul</span><span class="invisible">l/tobermory/index.html</span></a></p><p>Where to next? Vote via the poll attached as a reply.</p><p><a href="https://mastodon.scot/tags/Scotland" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Scotland</span></a> <a href="https://mastodon.scot/tags/IsleOfMull" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>IsleOfMull</span></a> <a href="https://mastodon.scot/tags/Mull" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Mull</span></a> <a href="https://mastodon.scot/tags/Tobermory" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Tobermory</span></a> <a href="https://mastodon.scot/tags/Harbour" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Harbour</span></a> <a href="https://mastodon.scot/tags/Argyll" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Argyll</span></a> <a href="https://mastodon.scot/tags/UndiscoveredScotland" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>UndiscoveredScotland</span></a></p>',
|
||||
'filtered': [],
|
||||
'reblog': None,
|
||||
'account': {'id': 109308679921192253,
|
||||
'username': 'UndisScot',
|
||||
'acct': 'UndisScot@mastodon.scot',
|
||||
'display_name': 'Undiscovered Scotland',
|
||||
'locked': False,
|
||||
'bot': False,
|
||||
'discoverable': True,
|
||||
'group': False,
|
||||
'created_at': datetime.datetime(2022, 11, 8, 0, 0, tzinfo=tzutc()),
|
||||
'note': '<p>Undiscovered Scotland is a combination of visitor guide, accommodation listing and business directory which aims to show you what the country is really like.</p>',
|
||||
'url': 'https://mastodon.scot/@UndisScot',
|
||||
'avatar': 'https://cdn.mastodon.org.uk/cache/accounts/avatars/109/308/679/921/192/253/original/c32d0bd7341746ec.jpg',
|
||||
'avatar_static': 'https://cdn.mastodon.org.uk/cache/accounts/avatars/109/308/679/921/192/253/original/c32d0bd7341746ec.jpg',
|
||||
'header': 'https://cdn.mastodon.org.uk/cache/accounts/headers/109/308/679/921/192/253/original/0d80ba57f8632e96.gif',
|
||||
'header_static': 'https://cdn.mastodon.org.uk/cache/accounts/headers/109/308/679/921/192/253/static/0d80ba57f8632e96.png',
|
||||
'followers_count': 3898,
|
||||
'following_count': 120,
|
||||
'statuses_count': 382,
|
||||
'last_status_at': datetime.datetime(2023, 1, 1, 0, 0),
|
||||
'emojis': [],
|
||||
'fields': [{'name': 'Website',
|
||||
'value': '<a href="https://www.undiscoveredscotland.co.uk" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://www.</span><span class="">undiscoveredscotland.co.uk</span><span class="invisible"></span></a>',
|
||||
'verified_at': '2022-12-08T08:28:32.376+00:00'}]},
|
||||
'media_attachments': [{'id': 109615047281836119,
|
||||
'type': 'image',
|
||||
'url': 'https://cdn.mastodon.org.uk/cache/media_attachments/files/109/615/047/281/836/119/original/2216c7554c0b8ed5.jpg',
|
||||
'preview_url': 'https://cdn.mastodon.org.uk/cache/media_attachments/files/109/615/047/281/836/119/small/2216c7554c0b8ed5.jpg',
|
||||
'remote_url': 'https://media.mastodon.scot/mastodon-scot-public/media_attachments/files/109/615/043/452/921/824/original/82a752e9a05da694.jpg',
|
||||
'preview_remote_url': None,
|
||||
'text_url': None,
|
||||
'meta': {'focus': {'x': 0.0, 'y': 0.0},
|
||||
'original': {'width': 1024,
|
||||
'height': 768,
|
||||
'size': '1024x768',
|
||||
'aspect': 1.3333333333333333},
|
||||
'small': {'width': 554,
|
||||
'height': 416,
|
||||
'size': '554x416',
|
||||
'aspect': 1.3317307692307692}},
|
||||
'description': 'Tobermory, the island capital of the Isle of Mull. The image shows a view down onto the harbour, looking along the length of Main Street. There are rooftops in the foreground and a row of buildings can be seen high on the hillside above the village, upper right in the picture. There is a twin masted sailing boat in the harbour in the left foreground.',
|
||||
'blurhash': 'UODTFiE3WFxbt-xvs.a~pKx]oJWX?wNHt7of'}],
|
||||
'mentions': [],
|
||||
'tags': [{'name': 'undiscoveredscotland',
|
||||
'url': 'https://mastodon.org.uk/tags/undiscoveredscotland'},
|
||||
{'name': 'argyll', 'url': 'https://mastodon.org.uk/tags/argyll'},
|
||||
{'name': 'harbour', 'url': 'https://mastodon.org.uk/tags/harbour'},
|
||||
{'name': 'tobermory', 'url': 'https://mastodon.org.uk/tags/tobermory'},
|
||||
{'name': 'mull', 'url': 'https://mastodon.org.uk/tags/mull'},
|
||||
{'name': 'IsleOfMull', 'url': 'https://mastodon.org.uk/tags/IsleOfMull'},
|
||||
{'name': 'scotland', 'url': 'https://mastodon.org.uk/tags/scotland'}],
|
||||
'emojis': [],
|
||||
'card': {'url': 'https://www.undiscoveredscotland.co.uk/mull/tobermory/index.html',
|
||||
'title': 'Tobermory Feature Page on Undiscovered Scotland',
|
||||
'description': 'Information about and images of Tobermory on Mull on Undiscovered Scotland.',
|
||||
'type': 'link',
|
||||
'author_name': '',
|
||||
'author_url': '',
|
||||
'provider_name': '',
|
||||
'provider_url': '',
|
||||
'html': '',
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'image': None,
|
||||
'embed_url': '',
|
||||
'blurhash': None},
|
||||
'poll': None},
|
||||
'account': {'id': 109392648174562172,
|
||||
'username': 'SecularJeffrey',
|
||||
'acct': 'SecularJeffrey@ohai.social',
|
||||
'display_name': 'JeffroTull',
|
||||
'locked': False,
|
||||
'bot': False,
|
||||
'discoverable': False,
|
||||
'group': False,
|
||||
'created_at': datetime.datetime(2022, 11, 11, 0, 0, tzinfo=tzutc()),
|
||||
'note': '<p>Semi retired. Living on colonized tribal land in the Pacific Northwest of North America. (((He/They/Him))). </p><p>On the cusp {born a late stage boomer, identify as early GenX}. Lapsed hippie. </p><p>Love is love. 🏳️\u200d🌈</p><p><a href="https://ohai.social/tags/AltText" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>AltText</span></a> <br><a href="https://ohai.social/tags/DescriptiveTex" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>DescriptiveTex</span></a><br><a href="https://ohai.social/tags/DeadHead" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>DeadHead</span></a><br><a href="https://ohai.social/tags/StarTrek" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>StarTrek</span></a> <br><a href="https://ohai.social/tags/StarWars" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>StarWars</span></a> <br><a href="https://ohai.social/tags/SciFi" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>SciFi</span></a><br><a href="https://ohai.social/tags/Photography" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Photography</span></a><br><a href="https://ohai.social/tags/Nature" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Nature</span></a><br><a href="https://ohai.social/tags/Memes" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Memes</span></a><br><a href="https://ohai.social/tags/Birds" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Birds</span></a> <br><a href="https://ohai.social/tags/Fossils" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Fossils</span></a><br><a href="https://ohai.social/tags/Space" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Space</span></a><br><a href="https://ohai.social/tags/Science" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Science</span></a><br><a href="https://ohai.social/tags/Amphibians" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Amphibians</span></a> <br><a href="https://ohai.social/tags/Reptiles" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Reptiles</span></a><br><a href="https://ohai.social/tags/Insects" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Insects</span></a><br><a href="https://ohai.social/tags/Invertebrates" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Invertebrates</span></a> <br><a href="https://ohai.social/tags/Archeology" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>Archeology</span></a> <br><a href="https://ohai.social/tags/History" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>History</span></a><br><a href="https://ohai.social/tags/AstroPhotography" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>AstroPhotography</span></a><br><a href="https://ohai.social/tags/VintageCars" class="mention hashtag" rel="nofollow noopener noreferrer" target="_blank">#<span>VintageCars</span></a> </p><p><a href="https://youtube.com/@JeffreyDuddles" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="">youtube.com/@JeffreyDuddles</span><span class="invisible"></span></a></p><p><a href="https://www.instagram.com/p/CcycJtnvlqF/?igshid=MDJmNzVkMjY=" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://www.</span><span class="ellipsis">instagram.com/p/CcycJtnvlqF/?i</span><span class="invisible">gshid=MDJmNzVkMjY=</span></a></p>',
|
||||
'url': 'https://ohai.social/@SecularJeffrey',
|
||||
'avatar': 'https://cdn.mastodon.org.uk/cache/accounts/avatars/109/392/648/174/562/172/original/cb6060134971c5f9.jpeg',
|
||||
'avatar_static': 'https://cdn.mastodon.org.uk/cache/accounts/avatars/109/392/648/174/562/172/original/cb6060134971c5f9.jpeg',
|
||||
'header': 'https://cdn.mastodon.org.uk/cache/accounts/headers/109/392/648/174/562/172/original/8b41057574e818ea.jpg',
|
||||
'header_static': 'https://cdn.mastodon.org.uk/cache/accounts/headers/109/392/648/174/562/172/original/8b41057574e818ea.jpg',
|
||||
'followers_count': 1766,
|
||||
'following_count': 4262,
|
||||
'statuses_count': 1615,
|
||||
'last_status_at': datetime.datetime(2023, 1, 1, 0, 0),
|
||||
'emojis': [],
|
||||
'fields': []},
|
||||
'media_attachments': [],
|
||||
'mentions': [],
|
||||
'tags': [],
|
||||
'emojis': [],
|
||||
'card': None,
|
||||
'poll': None}
|
||||
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
5
tests/test_kaemasto.py
Normal file
5
tests/test_kaemasto.py
Normal file
@ -0,0 +1,5 @@
|
||||
from urma import __version__
|
||||
|
||||
|
||||
def test_version():
|
||||
assert __version__ == '0.1.0'
|
||||
Loading…
Reference in New Issue
Block a user