Vorbereitung Release 0.2.0
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
"""
|
||||
add actors
|
||||
"""
|
||||
import logging.config
|
||||
import requests
|
||||
import re
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
def get_logger(level: int) -> logging.Logger:
|
||||
logging.config.dictConfig({
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': logging.StreamHandler,
|
||||
'level': logging.DEBUG,
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stdout'
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'urllib3.connectionpool': {
|
||||
'level': 'WARNING',
|
||||
'propagate': False,
|
||||
},
|
||||
'root': {
|
||||
'level': 'DEBUG',
|
||||
'handlers': ['console'],
|
||||
},
|
||||
},
|
||||
})
|
||||
logger = logging.getLogger(__file__)
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
logger.setLevel(logging.WARNING)
|
||||
case 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
case 2:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
case _:
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
return logger
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose)
|
||||
log.warning('kontor.add_actors started')
|
||||
log.debug('get all actors')
|
||||
response = requests.get("http://127.0.0.1:8800/api/media/actors")
|
||||
data = response.json()
|
||||
actors = {}
|
||||
for item in data:
|
||||
actor = {}
|
||||
actor['id'] = item['id']
|
||||
actor['name'] = item['name']
|
||||
actor['url'] = item['url']
|
||||
actors[item['url']] = actor
|
||||
log.debug(f'all actors: {actors}')
|
||||
|
||||
new_actor_list = [
|
||||
{ 'name': 'Herschel Savage', 'url': 'https://ge.xhamster.com/pornstars/herschel-savage'},
|
||||
{ 'name': 'Janey Robbins', 'url': 'https://ge.xhamster.com/pornstars/janey-robbins'},
|
||||
{ 'name': 'Kimberly Carson', 'url': 'https://ge.xhamster.com/pornstars/kimberly-carson'},
|
||||
{ 'name': 'Paul Thomas', 'url': 'https://ge.xhamster.com/pornstars/paul-thomas'},
|
||||
{ 'name': 'Shauna Grant', 'url': 'https://ge.xhamster.com/pornstars/shauna-grant'},
|
||||
{ 'name': 'Tara Aire', 'url': 'https://ge.xhamster.com/pornstars/tara-aire'},
|
||||
{ 'name': 'Kari Milla', 'url': 'https://ge.xhamster.com/pornstars/kari-milla'},
|
||||
{ 'name': 'Drago Gvozdik', 'url': 'https://ge.xhamster.com/pornstars/drago-gvozdik'},
|
||||
{ 'name': 'Cheyne Collins', 'url': 'https://ge.xhamster.com/pornstars/cheyne-collins'},
|
||||
{ 'name': 'Christian XXX', 'url': 'https://ge.xhamster.com/pornstars/christian-xxx'},
|
||||
{ 'name': 'Derrick Pierce', 'url': 'https://ge.xhamster.com/pornstars/derrick-pierce'},
|
||||
{ 'name': 'Holly Halston', 'url': 'https://ge.xhamster.com/pornstars/holly-halston'},
|
||||
{ 'name': 'Holly West', 'url': 'https://ge.xhamster.com/pornstars/holly-west'},
|
||||
{ 'name': 'Jarod Diamond', 'url': 'https://ge.xhamster.com/pornstars/jarod-diamond'},
|
||||
{ 'name': 'Kristina Cross', 'url': 'https://ge.xhamster.com/pornstars/kristina-cross'},
|
||||
{ 'name': 'Lee Stone', 'url': 'https://ge.xhamster.com/pornstars/lee-stone'},
|
||||
{ 'name': 'Monica Mayhem', 'url': 'https://ge.xhamster.com/pornstars/monica-mayhem'},
|
||||
{ 'name': 'Sienna West', 'url': 'https://ge.xhamster.com/pornstars/sienna-west'},
|
||||
{ 'name': 'Aria Carson', 'url': 'https://ge.xhamster.com/pornstars/aria-carson'},
|
||||
{ 'name': 'Britney Amber', 'url': 'https://ge.xhamster.com/pornstars/britney-amber'},
|
||||
{ 'name': 'Kit Mercer', 'url': 'https://ge.xhamster.com/pornstars/kit-mercer'},
|
||||
{ 'name': 'Riley Reyes', 'url': 'https://ge.xhamster.com/pornstars/riley-reyes'},
|
||||
{ 'name': 'Ryan Keely', 'url': 'https://ge.xhamster.com/pornstars/ryan-keely'},
|
||||
{ 'name': 'Alexandra Diamond', 'url': 'https://ge.xhamster.com/pornstars/alexandra-diamond'},
|
||||
{ 'name': 'Amanda Twice', 'url': 'https://ge.xhamster.com/pornstars/amanda-twice'},
|
||||
{ 'name': 'Choky Ice', 'url': 'https://ge.xhamster.com/pornstars/choky-ice'},
|
||||
{ 'name': 'Cindy Cox', 'url': 'https://ge.xhamster.com/pornstars/cindy-cox'},
|
||||
{ 'name': 'Kelly White', 'url': 'https://ge.xhamster.com/pornstars/kelly-white'},
|
||||
{ 'name': 'Mike Foster', 'url': 'https://ge.xhamster.com/pornstars/mike-foster'},
|
||||
{ 'name': 'Susie Sorrento', 'url': 'https://ge.xhamster.com/pornstars/susie-sorrento'},
|
||||
{ 'name': 'Lara Sanchez', 'url': 'https://ge.xhamster.com/pornstars/lara-sanchez'},
|
||||
{ 'name': 'Alicia Williams', 'url': 'https://ge.xhamster.com/pornstars/alicia-williams'},
|
||||
{ 'name': 'Dirty Tina', 'url': 'https://ge.xhamster.com/pornstars/dirty-tina'},
|
||||
{ 'name': 'Andrew Andretti', 'url': 'https://ge.xhamster.com/pornstars/andrew-andretti'},
|
||||
{ 'name': 'Dick Nasty', 'url': 'https://ge.xhamster.com/pornstars/dick-nasty'},
|
||||
{ 'name': 'Vicky Vette', 'url': 'https://ge.xhamster.com/pornstars/vicky-vette'},
|
||||
{ 'name': 'Alex Gonz', 'url': 'https://ge.xhamster.com/pornstars/alex-gonz'},
|
||||
{ 'name': 'Audrey Hollander', 'url': 'https://ge.xhamster.com/pornstars/audrey-hollander'},
|
||||
{ 'name': 'Desiree Diamond', 'url': 'https://ge.xhamster.com/pornstars/desiree-diamond'},
|
||||
{ 'name': 'Lexi Bardot', 'url': 'https://ge.xhamster.com/pornstars/lexi-bardot'},
|
||||
{ 'name': 'Adam Ocelot', 'url': 'https://ge.xhamster.com/pornstars/adam-ocelot'},
|
||||
{ 'name': 'Scarlett Hampton', 'url': 'https://ge.xhamster.com/pornstars/scarlett-hampton'},
|
||||
{ 'name': 'Laetitia', 'url': 'https://ge.xhamster.com/pornstars/laetitia'},
|
||||
{ 'name': 'Raffaela Anderson', 'url': 'https://ge.xhamster.com/pornstars/raffaela-anderson'},
|
||||
{ 'name': 'Michael Swayze', 'url': 'https://ge.xhamster.com/pornstars/michael-swayze'},
|
||||
{ 'name': 'Winter Jade', 'url': 'https://ge.xhamster.com/pornstars/winter-jade'},
|
||||
{ 'name': 'Celine Noiret', 'url': 'https://ge.xhamster.com/pornstars/celine-noiret'},
|
||||
{ 'name': 'Aurora Snow', 'url': 'https://ge.xhamster.com/pornstars/aurora-snow'},
|
||||
{ 'name': 'Bree Brooks', 'url': 'https://ge.xhamster.com/pornstars/bree-brooks'},
|
||||
{ 'name': 'Flick Shagwell', 'url': 'https://ge.xhamster.com/pornstars/flick-shagwell'},
|
||||
{ 'name': 'Noname Jane', 'url': 'https://ge.xhamster.com/pornstars/noname-jane'},
|
||||
{ 'name': 'Alex Magni', 'url': 'https://ge.xhamster.com/pornstars/alex-magni'},
|
||||
{ 'name': 'Maeva Dream', 'url': 'https://ge.xhamster.com/pornstars/maeva-dream'},
|
||||
{ 'name': 'Squirty Alice', 'url': 'https://ge.xhamster.com/pornstars/squirty-alice'},
|
||||
{ 'name': 'Mandy Rhea', 'url': 'https://ge.xhamster.com/pornstars/mandy-rhea'},
|
||||
{ 'name': 'Alessia Donati', 'url': 'https://ge.xhamster.com/pornstars/alessia-donati'},
|
||||
{ 'name': 'Pierre DJ', 'url': 'https://ge.xhamster.com/pornstars/pierre-dj'},
|
||||
{ 'name': 'Veronica Belli', 'url': 'https://ge.xhamster.com/pornstars/veronica-belli'},
|
||||
{ 'name': 'Christiana Cinn', 'url': 'https://ge.xhamster.com/pornstars/christiana-cinn'},
|
||||
{ 'name': 'Jasmine Jae', 'url': 'https://ge.xhamster.com/pornstars/jasmine-jae'},
|
||||
{ 'name': 'Jay Smooth', 'url': 'https://ge.xhamster.com/pornstars/jay-smooth'},
|
||||
{ 'name': 'Natalie Knight', 'url': 'https://ge.xhamster.com/pornstars/natalie-knight'},
|
||||
{ 'name': 'Joshua Lewis', 'url': 'https://ge.xhamster.com/pornstars/joshua-lewis'},
|
||||
{ 'name': 'Lauren Phillips', 'url': 'https://ge.xhamster.com/pornstars/lauren-phillips'},
|
||||
{ 'name': 'Matt Cash', 'url': 'https://ge.xhamster.com/pornstars/matt-cash'},
|
||||
{ 'name': 'Nickey Huntsman', 'url': 'https://ge.xhamster.com/pornstars/nickey-huntsman'},
|
||||
{ 'name': 'Jade Sin', 'url': 'https://ge.xhamster.com/pornstars/jade-sin'},
|
||||
{ 'name': 'Wein Lewis', 'url': 'https://ge.xhamster.com/pornstars/wein-lewis'},
|
||||
{ 'name': 'Megan Murkovski', 'url': 'https://ge.xhamster.com/pornstars/megan-murkovski'},
|
||||
{ 'name': 'Tara Wild', 'url': 'https://ge.xhamster.com/pornstars/tara-wild'},
|
||||
{ 'name': 'Lana Roy', 'url': 'https://ge.xhamster.com/pornstars/lana-roy'},
|
||||
{ 'name': 'Nick Moreno', 'url': 'https://ge.xhamster.com/pornstars/nick-moreno'},
|
||||
{ 'name': 'Brad Armstrong', 'url': 'https://ge.xhamster.com/pornstars/brad-armstrong'},
|
||||
{ 'name': 'Kaylani Lei', 'url': 'https://ge.xhamster.com/pornstars/kaylani-lei'},
|
||||
{ 'name': 'Deborah Wells', 'url': 'https://ge.xhamster.com/pornstars/deborah-wells'},
|
||||
{ 'name': 'Judith Ramirez', 'url': 'https://ge.xhamster.com/pornstars/judith-ramirez'},
|
||||
{ 'name': 'Richard Langin', 'url': 'https://ge.xhamster.com/pornstars/richard-langin'},
|
||||
{ 'name': 'Simona Valli', 'url': 'https://ge.xhamster.com/pornstars/simona-valli'},
|
||||
{ 'name': 'Mandy Dee', 'url': 'https://ge.xhamster.com/pornstars/mandy-dee'},
|
||||
{ 'name': 'Allison Wyte', 'url': 'https://ge.xhamster.com/pornstars/allison-wyte'},
|
||||
{ 'name': 'Ben English', 'url': 'https://ge.xhamster.com/pornstars/ben-english'},
|
||||
{ 'name': 'Chuck Martino', 'url': 'https://ge.xhamster.com/pornstars/chuck-martino'},
|
||||
{ 'name': 'Erika Kole', 'url': 'https://ge.xhamster.com/pornstars/erika-kole'},
|
||||
{ 'name': 'Katie Morgan', 'url': 'https://ge.xhamster.com/pornstars/katie-morgan'},
|
||||
{ 'name': 'Misty Parks', 'url': 'https://ge.xhamster.com/pornstars/misty-parks'},
|
||||
{ 'name': 'Mr. Pete', 'url': 'https://ge.xhamster.com/pornstars/mr-pete'},
|
||||
{ 'name': 'Candie Luciani', 'url': 'https://ge.xhamster.com/pornstars/candie-luciani'},
|
||||
{ 'name': 'Clara Mia', 'url': 'https://ge.xhamster.com/pornstars/clara-mia'},
|
||||
{ 'name': 'Tommy Cabrio', 'url': 'https://ge.xhamster.com/pornstars/tommy-cabrio'},
|
||||
{ 'name': 'Nathan Bronson', 'url': 'https://ge.xhamster.com/pornstars/nathan-bronson'},
|
||||
{ 'name': 'Octavia Red', 'url': 'https://ge.xhamster.com/pornstars/octavia-red'},
|
||||
{ 'name': 'Big George', 'url': 'https://ge.xhamster.com/pornstars/big-george'},
|
||||
{ 'name': 'Cassy Young', 'url': 'https://ge.xhamster.com/pornstars/cassy-young'},
|
||||
{ 'name': 'Egon Kowalski', 'url': 'https://ge.xhamster.com/pornstars/egon-kowalski'},
|
||||
{ 'name': 'Mark Aurel', 'url': 'https://ge.xhamster.com/pornstars/mark-aurel'},
|
||||
{ 'name': 'Samy Fox', 'url': 'https://ge.xhamster.com/pornstars/samy-fox'},
|
||||
{ 'name': 'Valeria Jones', 'url': 'https://ge.xhamster.com/pornstars/valeria-jones'},
|
||||
{ 'name': 'Dieter Von Stein', 'url': 'https://ge.xhamster.com/pornstars/dieter-von-stein'},
|
||||
{ 'name': 'Donna Bell', 'url': 'https://ge.xhamster.com/pornstars/donna-bell'},
|
||||
{ 'name': 'Molly Jane', 'url': 'https://ge.xhamster.com/pornstars/molly-jane'},
|
||||
{ 'name': 'Mark Zane', 'url': 'https://ge.xhamster.com/pornstars/mark-zane'},
|
||||
{ 'name': 'Titus Steel', 'url': 'https://ge.xhamster.com/pornstars/titus-steel'},
|
||||
{ 'name': 'Mackenzie Mace', 'url': 'https://ge.xhamster.com/pornstars/mackenzie-mace'},
|
||||
{ 'name': 'Christina Lang', 'url': 'https://ge.xhamster.com/pornstars/christina-lang'},
|
||||
{ 'name': 'Jens Modena', 'url': 'https://ge.xhamster.com/pornstars/jens-modena'},
|
||||
{ 'name': 'Karma Rosenberg', 'url': 'https://ge.xhamster.com/pornstars/karma-rosenberg'},
|
||||
{ 'name': 'Dani Jensen', 'url': 'https://ge.xhamster.com/pornstars/dani-jensen'},
|
||||
{ 'name': 'Crystal Ray', 'url': 'https://ge.xhamster.com/pornstars/crystal-ray'},
|
||||
{ 'name': 'Andrea Szabo', 'url': 'https://ge.xhamster.com/pornstars/andrea-szabo'},
|
||||
{ 'name': 'Desiree Barclay', 'url': 'https://ge.xhamster.com/pornstars/desiree-barclay'},
|
||||
{ 'name': 'Jada Stevens', 'url': 'https://ge.xhamster.com/pornstars/jada-stevens'},
|
||||
{ 'name': 'Syren Demer', 'url': 'https://ge.xhamster.com/pornstars/syren-demer'},
|
||||
{ 'name': 'Ara Mix', 'url': 'https://ge.xhamster.com/pornstars/ara-mix'},
|
||||
{ 'name': 'Redneck John', 'url': 'https://ge.xhamster.com/pornstars/redneck-john'},
|
||||
{ 'name': 'Allysin Chaynes', 'url': 'https://ge.xhamster.com/pornstars/allysin-chaynes'},
|
||||
{ 'name': 'Aspen Brock', 'url': 'https://ge.xhamster.com/pornstars/aspen-brock'},
|
||||
{ 'name': 'Kaylynn', 'url': 'https://ge.xhamster.com/pornstars/kaylynn'},
|
||||
{ 'name': 'Monica Cameron', 'url': 'https://ge.xhamster.com/pornstars/monica-cameron'},
|
||||
{ 'name': 'Sheila Rossi', 'url': 'https://ge.xhamster.com/pornstars/sheila-rossi'},
|
||||
{ 'name': 'Alex Ginger', 'url': 'https://ge.xhamster.com/pornstars/alex-ginger'},
|
||||
{ 'name': 'Ally Style', 'url': 'https://ge.xhamster.com/pornstars/ally-style'},
|
||||
{ 'name': 'Paola Mike', 'url': 'https://ge.xhamster.com/pornstars/paola-mike'},
|
||||
{ 'name': 'Jeanette Littledove', 'url': 'https://ge.xhamster.com/pornstars/jeanette-littledove'},
|
||||
{ 'name': 'Melissa Melendez', 'url': 'https://ge.xhamster.com/pornstars/melissa-melendez'},
|
||||
{ 'name': 'Peter North', 'url': 'https://ge.xhamster.com/pornstars/peter-north'},
|
||||
{ 'name': 'Siobhan Hunter', 'url': 'https://ge.xhamster.com/pornstars/siobhan-hunter'},
|
||||
{ 'name': 'Tami White', 'url': 'https://ge.xhamster.com/pornstars/tami-white'},
|
||||
{ 'name': 'Tracey Adams', 'url': 'https://ge.xhamster.com/pornstars/tracey-adams'},
|
||||
{ 'name': 'Ashley Haze', 'url': 'https://ge.xhamster.com/pornstars/ashley-haze'},
|
||||
{ 'name': 'Cailey Taylor', 'url': 'https://ge.xhamster.com/pornstars/cailey-taylor'},
|
||||
{ 'name': 'Eve Laurence', 'url': 'https://ge.xhamster.com/pornstars/eve-laurence'},
|
||||
{ 'name': 'Naudia Nyce', 'url': 'https://ge.xhamster.com/pornstars/naudia-nyce'},
|
||||
{ 'name': 'Candy Apples', 'url': 'https://ge.xhamster.com/pornstars/candy-apples'},
|
||||
{ 'name': 'Kate Rich', 'url': 'https://ge.xhamster.com/pornstars/kate-rich'},
|
||||
{ 'name': 'Aniko Kaposi', 'url': 'https://ge.xhamster.com/pornstars/aniko-kaposi'},
|
||||
{ 'name': 'Beatrice Poggi', 'url': 'https://ge.xhamster.com/pornstars/beatrice-poggi'},
|
||||
{ 'name': 'Erika Bella', 'url': 'https://ge.xhamster.com/pornstars/erika-bella'},
|
||||
{ 'name': 'Nikita Gross', 'url': 'https://ge.xhamster.com/pornstars/nikita-gross'},
|
||||
{ 'name': 'Ursula Moore', 'url': 'https://ge.xhamster.com/pornstars/ursula-moore'},
|
||||
{ 'name': 'Caty Kiss', 'url': 'https://ge.xhamster.com/pornstars/caty-kiss'},
|
||||
{ 'name': 'Light Fairy', 'url': 'https://ge.xhamster.com/pornstars/light-fairy'},
|
||||
{ 'name': 'Flame', 'url': 'https://ge.xhamster.com/pornstars/flame'},
|
||||
{ 'name': 'Tiffany Tatum', 'url': 'https://ge.xhamster.com/pornstars/tiffany-tatum'},
|
||||
{ 'name': 'Alyson Sykes', 'url': 'https://ge.xhamster.com/pornstars/alyson-sykes'},
|
||||
{ 'name': 'Jenifer Stone', 'url': 'https://ge.xhamster.com/pornstars/jenifer-stone'},
|
||||
{ 'name': 'Lucy Love', 'url': 'https://ge.xhamster.com/pornstars/lucy-love'},
|
||||
{ 'name': 'Thomas Stone', 'url': 'https://ge.xhamster.com/pornstars/thomas-stone'},
|
||||
{ 'name': 'Nikki Babe', 'url': 'https://ge.xhamster.com/pornstars/nikki-babe'},
|
||||
{ 'name': 'Tyra Misoux', 'url': 'https://ge.xhamster.com/pornstars/tyra-misoux'},
|
||||
{ 'name': 'Kenzie Reeves', 'url': 'https://ge.xhamster.com/pornstars/kenzie-reeves'},
|
||||
{ 'name': 'Anissa Kate', 'url': 'https://ge.xhamster.com/pornstars/anissa-kate'},
|
||||
{ 'name': 'Anna Polina', 'url': 'https://ge.xhamster.com/pornstars/anna-polina'},
|
||||
{ 'name': 'Kimber Delice', 'url': 'https://ge.xhamster.com/pornstars/kimber-delice'},
|
||||
{ 'name': 'Lucy Heart', 'url': 'https://ge.xhamster.com/pornstars/lucy-heart'},
|
||||
{ 'name': 'John Strong', 'url': 'https://ge.xhamster.com/pornstars/john-strong'},
|
||||
{ 'name': 'Markus Dupree', 'url': 'https://ge.xhamster.com/pornstars/markus-dupree'},
|
||||
{ 'name': 'Mick Blue', 'url': 'https://ge.xhamster.com/pornstars/mick-blue'},
|
||||
{ 'name': 'Natasha Nice', 'url': 'https://ge.xhamster.com/pornstars/natasha-nice'},
|
||||
{ 'name': 'Lena Reif', 'url': 'https://ge.xhamster.com/pornstars/lena-reif'},
|
||||
{ 'name': 'Sonia Paganini', 'url': 'https://ge.xhamster.com/pornstars/sonia-paganini'},
|
||||
{ 'name': 'Demi Hawks', 'url': 'https://ge.xhamster.com/pornstars/demi-hawks'},
|
||||
{ 'name': 'Juan El Caballo Loco', 'url': 'https://ge.xhamster.com/pornstars/juan-el-caballo-loco'},
|
||||
{ 'name': 'Mike Mancini', 'url': 'https://ge.xhamster.com/pornstars/mike-mancini'},
|
||||
{ 'name': 'Millie Morgan', 'url': 'https://ge.xhamster.com/pornstars/millie-morgan'},
|
||||
{ 'name': 'Richard Allan', 'url': 'https://ge.xhamster.com/pornstars/richard-allan'},
|
||||
{ 'name': 'Damon Dice', 'url': 'https://ge.xhamster.com/pornstars/damon-dice'},
|
||||
{ 'name': 'Sera Ryder', 'url': 'https://ge.xhamster.com/pornstars/sera-ryder'},
|
||||
{ 'name': 'Zoltan Kaabai', 'url': 'https://ge.xhamster.com/pornstars/zoltan-kabai'},
|
||||
{ 'name': 'Cathy Heaven', 'url': 'https://ge.xhamster.com/pornstars/cathy-heaven'},
|
||||
{ 'name': 'Coco Lovelock', 'url': 'https://ge.xhamster.com/pornstars/coco-lovelock'},
|
||||
{ 'name': 'Percy Sires', 'url': 'https://ge.xhamster.com/pornstars/percy-sires'},
|
||||
{ 'name': 'Meridian', 'url': 'https://ge.xhamster.com/pornstars/meridian'},
|
||||
{ 'name': 'Pascal St. James', 'url': 'https://ge.xhamster.com/pornstars/pascal-st-james'},
|
||||
{ 'name': 'Red Fox', 'url': 'https://ge.xhamster.com/pornstars/red-fox'},
|
||||
{ 'name': 'Tony Art', 'url': 'https://ge.xhamster.com/pornstars/tony-art'},
|
||||
{ 'name': 'Addison Lee', 'url': 'https://ge.xhamster.com/pornstars/addison-lee'},
|
||||
{ 'name': 'Daria Glover', 'url': 'https://ge.xhamster.com/pornstars/daria-glover'},
|
||||
{ 'name': 'Mandy Bright', 'url': 'https://ge.xhamster.com/pornstars/mandy-bright'},
|
||||
{ 'name': 'Antonia Sainz', 'url': 'https://ge.xhamster.com/pornstars/antonia-sainz'},
|
||||
{ 'name': 'Nicole Love', 'url': 'https://ge.xhamster.com/pornstars/nicole-love'},
|
||||
{ 'name': 'Sarah Kay', 'url': 'https://ge.xhamster.com/pornstars/sarah-kay'},
|
||||
{ 'name': 'Judith Kostner', 'url': 'https://ge.xhamster.com/pornstars/judith-kostner'},
|
||||
{ 'name': 'Maria Bellucci', 'url': 'https://ge.xhamster.com/pornstars/maria-bellucci'},
|
||||
{ 'name': 'Melissa Monet', 'url': 'https://ge.xhamster.com/pornstars/melissa-monet'},
|
||||
{ 'name': 'Stephanie Cane', 'url': 'https://ge.xhamster.com/pornstars/stephanie-cane'},
|
||||
{ 'name': 'Will Steiger', 'url': 'https://ge.xhamster.com/pornstars/will-steiger'},
|
||||
{ 'name': 'Katty West', 'url': 'https://ge.xhamster.com/pornstars/katty-west'},
|
||||
{ 'name': 'Jean Pallett', 'url': 'https://ge.xhamster.com/pornstars/jean-pallett'},
|
||||
{ 'name': 'Conny Dachs', 'url': 'https://ge.xhamster.com/pornstars/conny-dachs'},
|
||||
{ 'name': 'Juicy Leyla', 'url': 'https://ge.xhamster.com/pornstars/juicy-leyla'},
|
||||
{ 'name': 'Mandy Mystery', 'url': 'https://ge.xhamster.com/pornstars/mandy-mystery'},
|
||||
{ 'name': 'Jack Vegas', 'url': 'https://ge.xhamster.com/pornstars/jack-vegas'},
|
||||
{ 'name': 'Jessica Ryan', 'url': 'https://ge.xhamster.com/pornstars/jessica-ryan'},
|
||||
{ 'name': 'Nathan Bronson', 'url': 'https://ge.xhamster.com/pornstars/nathan-bronson'},
|
||||
{ 'name': 'Lexi Lore', 'url': 'https://ge.xhamster.com/pornstars/lexi-lore'},
|
||||
{ 'name': 'Molly Little', 'url': 'https://ge.xhamster.com/pornstars/molly-little'},
|
||||
{ 'name': 'Dolly Leigh', 'url': 'https://ge.xhamster.com/pornstars/dolly-leigh'},
|
||||
{ 'name': 'Marcus London', 'url': 'https://ge.xhamster.com/pornstars/marcus-london'},
|
||||
{ 'name': 'Kendra Sunderland', 'url': 'https://ge.xhamster.com/pornstars/kendra-sunderland'},
|
||||
{ 'name': 'Manuel Ferrara', 'url': 'https://ge.xhamster.com/pornstars/manuel-ferrara'},
|
||||
{ 'name': 'Kelly Trump', 'url': 'https://ge.xhamster.com/pornstars/kelly-trump'},
|
||||
{ 'name': 'Angelica Heaven', 'url': 'https://ge.xhamster.com/pornstars/angelica-heaven'},
|
||||
{ 'name': 'Luna Lynx', 'url': 'https://ge.xhamster.com/pornstars/luna-lynx'},
|
||||
{ 'name': 'Princess Lili', 'url': 'https://ge.xhamster.com/pornstars/princess-lili'},
|
||||
{ 'name': 'Alexa Wild', 'url': 'https://ge.xhamster.com/pornstars/alexa-wild'},
|
||||
{ 'name': 'Jessyka Swan', 'url': 'https://ge.xhamster.com/pornstars/jessyka-swan'},
|
||||
{ 'name': 'Nikky Thorne', 'url': 'https://ge.xhamster.com/pornstars/nikky-thorne'},
|
||||
{ 'name': 'Tristan Summers', 'url': 'https://ge.xhamster.com/pornstars/tristan-summers'},
|
||||
{ 'name': 'Rhaya Shyne', 'url': 'https://ge.xhamster.com/pornstars/rhaya-shyne'},
|
||||
{ 'name': 'Desiree West', 'url': 'https://ge.xhamster.com/pornstars/desiree-west'},
|
||||
{ 'name': 'Joan Devlon', 'url': 'https://ge.xhamster.com/pornstars/joan-devlon'},
|
||||
{ 'name': 'Jodi Thorpe', 'url': 'https://ge.xhamster.com/pornstars/jodi-thorpe'},
|
||||
{ 'name': 'Laurien Dominique', 'url': 'https://ge.xhamster.com/pornstars/laurien-dominique'},
|
||||
{ 'name': 'Paul Scharf', 'url': 'https://ge.xhamster.com/pornstars/paul-scharf'},
|
||||
{ 'name': 'Ray Wells', 'url': 'https://ge.xhamster.com/pornstars/ray-wells'},
|
||||
{ 'name': 'Sandy Carey', 'url': 'https://ge.xhamster.com/pornstars/sandy-carey'},
|
||||
{ 'name': 'Spender Travis', 'url': 'https://ge.xhamster.com/pornstars/spender-travis'},
|
||||
{ 'name': 'Starlyn Simone', 'url': 'https://ge.xhamster.com/pornstars/starlyn-simone'},
|
||||
{ 'name': 'Uschi Digard', 'url': 'https://ge.xhamster.com/pornstars/uschi-digard'},
|
||||
{ 'name': 'Katarina Muti', 'url': 'https://ge.xhamster.com/pornstars/katarina-muti'},
|
||||
{ 'name': 'Julia Power', 'url': 'https://ge.xhamster.com/pornstars/julia-power'},
|
||||
{ 'name': 'Salma De Nora', 'url': 'https://ge.xhamster.com/pornstars/salma-de-nora'},
|
||||
{ 'name': 'Valeria Jones', 'url': 'https://ge.xhamster.com/pornstars/valeria-jones'},
|
||||
{ 'name': 'Brandy Canyon', 'url': 'https://ge.xhamster.com/pornstars/brandy-canyon'},
|
||||
{ 'name': 'Marie Berger', 'url': 'https://ge.xhamster.com/pornstars/marie-berger'},
|
||||
{ 'name': 'Luna Rishi', 'url': 'https://ge.xhamster.com/pornstars/luna-rishi'},
|
||||
{ 'name': 'Colleen Brennan', 'url': 'https://ge.xhamster.com/pornstars/colleen-brennan'},
|
||||
{ 'name': 'Roxanne Brewer', 'url': 'https://ge.xhamster.com/pornstars/roxanne-brewer'},
|
||||
{ 'name': 'Elle Denay', 'url': 'https://ge.xhamster.com/pornstars/elle-denay'},
|
||||
{ 'name': 'Juan Largo', 'url': 'https://ge.xhamster.com/pornstars/juan-largo'},
|
||||
{ 'name': 'Codey Steele', 'url': 'https://ge.xhamster.com/pornstars/codey-steele'},
|
||||
{ 'name': 'Laney Grey', 'url': 'https://ge.xhamster.com/pornstars/laney-grey'},
|
||||
{ 'name': 'Stirling Cooper', 'url': 'https://ge.xhamster.com/pornstars/stirling-cooper'},
|
||||
{ 'name': 'Lana Smalls', 'url': 'https://ge.xhamster.com/pornstars/lana-smalls'},
|
||||
{ 'name': 'Alex Sanders', 'url':'https://ge.xhamster.com/pornstars/alex-sanders'},
|
||||
{ 'name': 'Felecia Danay', 'url':'https://ge.xhamster.com/pornstars/felecia-danay'},
|
||||
{ 'name': 'Lita Chase', 'url':'https://ge.xhamster.com/pornstars/lita-chase'},
|
||||
{ 'name': 'Mark Ashley', 'url':'https://ge.xhamster.com/pornstars/mark-ashley'},
|
||||
{ 'name': 'Phyllisha Anne', 'url':'https://ge.xhamster.com/pornstars/phyllisha-anne'},
|
||||
{ 'name': 'Ryan Conner', 'url':'https://ge.xhamster.com/pornstars/ryan-conner'},
|
||||
{ 'name': 'Tanya Danielle', 'url':'https://ge.xhamster.com/pornstars/tanya-danielle'},
|
||||
{ 'name': 'Jessica Moore', 'url':'https://ge.xhamster.com/pornstars/jessica-moore'},
|
||||
{ 'name': 'Mike Angelo', 'url':'https://ge.xhamster.com/pornstars/mike-angelo'},
|
||||
{ 'name': 'Morgan Moon', 'url':'https://ge.xhamster.com/pornstars/morgan-moon'},
|
||||
{ 'name': 'Tyler Steel', 'url':'https://ge.xhamster.com/pornstars/tyler-steel'},
|
||||
{ 'name': 'Abella Danger', 'url':'https://ge.xhamster.com/pornstars/abella-danger'},
|
||||
{ 'name': 'Alex Jett', 'url':'https://ge.xhamster.com/pornstars/alex-jett'},
|
||||
{ 'name': 'Alyson Queen', 'url':'https://ge.xhamster.com/pornstars/alyson-queen'},
|
||||
{ 'name': 'Antynia Rouge', 'url':'https://ge.xhamster.com/pornstars/antynia-rouge'},
|
||||
{ 'name': 'Bea Dumas', 'url':'https://ge.xhamster.com/pornstars/bea-dumas'},
|
||||
{ 'name': 'Callie Black', 'url':'https://ge.xhamster.com/pornstars/callie-black'},
|
||||
{ 'name': 'Caroline Cage', 'url':'https://ge.xhamster.com/pornstars/caroline-cage'},
|
||||
{ 'name': 'Cindy Dollar', 'url':'https://ge.xhamster.com/pornstars/cindy-dollar'},
|
||||
{ 'name': 'Crystal Frost', 'url':'https://ge.xhamster.com/pornstars/crystal-frost'},
|
||||
{ 'name': 'Fanny Steel', 'url':'https://ge.xhamster.com/pornstars/fanny-steel'},
|
||||
{ 'name': 'Gia Derza', 'url':'https://ge.xhamster.com/pornstars/gia-derza'},
|
||||
{ 'name': 'Horst Baron', 'url':'https://ge.xhamster.com/pornstars/horst-baron'},
|
||||
{ 'name': 'Jasmine Rouge', 'url':'https://ge.xhamster.com/pornstars/jasmine-rouge'},
|
||||
{ 'name': 'Jean-Pierre Armand', 'url':'https://ge.xhamster.com/pornstars/jean-pierre-armand'},
|
||||
{ 'name': 'Jessa Rhodes', 'url':'https://ge.xhamster.com/pornstars/jessa-rhodes'},
|
||||
{ 'name': 'Leonie Saint', 'url':'https://ge.xhamster.com/pornstars/leonie-saint'},
|
||||
{ 'name': 'Linda Ray', 'url':'https://ge.xhamster.com/pornstars/linda-ray'},
|
||||
{ 'name': 'Luca Ferrero', 'url':'https://ge.xhamster.com/pornstars/luca-ferrero'},
|
||||
{ 'name': 'Paris Pink', 'url':'https://ge.xhamster.com/pornstars/paris-pink'},
|
||||
{ 'name': 'Pavlina Stejskalova', 'url':'https://ge.xhamster.com/pornstars/pavlina-stejskalova'},
|
||||
{ 'name': 'Phoenix Marie', 'url':'https://ge.xhamster.com/pornstars/phoenix-marie'},
|
||||
{ 'name': 'Ricky Spanish', 'url':'https://ge.xhamster.com/pornstars/ricky-spanish'},
|
||||
{ 'name': 'Rumika Powers', 'url':'https://ge.xhamster.com/pornstars/rumika-powers'},
|
||||
{ 'name': 'Sara Blonde', 'url':'https://ge.xhamster.com/pornstars/sara-blonde'},
|
||||
{ 'name': 'Sean Lawless', 'url':'https://ge.xhamster.com/pornstars/sean-lawless'},
|
||||
{ 'name': 'Seth Gamble', 'url':'https://ge.xhamster.com/pornstars/seth-gamble'},
|
||||
{ 'name': 'Siri Dahl', 'url':'https://ge.xhamster.com/pornstars/siri-dahl'},
|
||||
{ 'name': 'Stephie Staar', 'url':'https://ge.xhamster.com/pornstars/stephie-staar'},
|
||||
{ 'name': 'Steve Holmes', 'url':'https://ge.xhamster.com/pornstars/steve-holmes'},
|
||||
{ 'name': 'Suzette Dale', 'url':'https://ge.xhamster.com/pornstars/suzette-dale'},
|
||||
{ 'name': 'Uncle George', 'url':'https://ge.xhamster.com/pornstars/uncle-george'},
|
||||
{ 'name': 'Winnie', 'url':'https://ge.xhamster.com/pornstars/winnie'},
|
||||
{ 'name': 'Zenza Raggi', 'url':'https://ge.xhamster.com/pornstars/zenza-raggi'},
|
||||
{ 'name': 'Zorah White', 'url':'https://ge.xhamster.com/pornstars/zorah-white'},
|
||||
{ 'name': 'Marilyn Jess', 'url':'https://ge.xhamster.com/pornstars/marilyn-jess'},
|
||||
{ 'name': 'Alexis Capri', 'url':'https://ge.xhamster.com/pornstars/alexis-capri'},
|
||||
]
|
||||
|
||||
|
||||
for new_actor in new_actor_list:
|
||||
if new_actor['url'] in actors:
|
||||
log.warning(f"Actor {new_actor['url']} already persisted")
|
||||
continue
|
||||
actor_response = requests.post(f"http://127.0.0.1:8800/api/media/actors", json=new_actor)
|
||||
log.warning(f"add status: {actor_response.status_code}")
|
||||
if actor_response.status_code == 201:
|
||||
log.warning(f"add Actor {new_actor['url']} to existing actor list")
|
||||
actors[new_actor['url']] = new_actor
|
||||
actor_data = actor_response.json()
|
||||
log.warning(f"Actor {actor_data} persisted")
|
||||
log.warning('kontor.add_actors finished')
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
read file with URLs and store in DB
|
||||
"""
|
||||
import logging.config
|
||||
import requests
|
||||
import yaml
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from pathlib import Path
|
||||
from platformdirs import PlatformDirs
|
||||
from proton import Message, Event
|
||||
from proton.handlers import MessagingHandler
|
||||
from proton.reactor import Container
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('-u', '--url', help='link')
|
||||
parser.add_argument('--video', help='store Url as VideoFile', action="store_true")
|
||||
parser.add_argument("--api", help="use Kontor API", action="store_true")
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
def get_logger(level: int, config: str):
|
||||
dirs = PlatformDirs(config)
|
||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
||||
with open(logging_config, 'rt') as f:
|
||||
configDict = yaml.safe_load(f.read())
|
||||
logging.config.dictConfig(configDict)
|
||||
logger = logging.getLogger('development')
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
logger.setLevel(logging.INFO)
|
||||
case 1:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
case _:
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
return logger
|
||||
|
||||
class AddLinkMessage(MessagingHandler):
|
||||
def __init__(self, server, url, log):
|
||||
super(AddLinkMessage, self).__init__()
|
||||
log.info("create AddLinkMessage")
|
||||
self.server = server
|
||||
self.address = "add_link_file"
|
||||
self.url = url
|
||||
self.log = log
|
||||
|
||||
def on_start(self, event: Event):
|
||||
self.log.info("Connection...")
|
||||
conn = event.container.connect(self.server, user="artemis", password="artemis")
|
||||
event.container.create_sender(conn, self.address)
|
||||
|
||||
def on_connection_error(self, event: Event) -> None:
|
||||
self.log.info(f"error: {event}")
|
||||
|
||||
def on_sendable(self, event: Event):
|
||||
self.log.info("send message")
|
||||
event.sender.send(Message(body=self.url, address=self.address, content_type="text/json"))
|
||||
event.connection.close()
|
||||
event.sender.close()
|
||||
|
||||
def on_accepted(self, event: Event) -> None:
|
||||
self.log.info(f"accepted: {event}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose, args.config)
|
||||
logger.info('kontor.add_link started')
|
||||
link: str = args.url
|
||||
data = {"url": link}
|
||||
if args.api:
|
||||
if args.video:
|
||||
request: str = "http://127.0.0.1:8800/api/video/files"
|
||||
else:
|
||||
request: str = "http://127.0.0.1:8800/api/media/files"
|
||||
response = requests.post(request, json=data)
|
||||
logger.info(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
else:
|
||||
Container(AddLinkMessage("amqp://127.0.0.1:5672", data, logger)).run()
|
||||
logger.info('kontor.add_link finished')
|
||||
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
read file with links and store it in DB
|
||||
"""
|
||||
from datetime import datetime
|
||||
import logging.config
|
||||
import re
|
||||
from typing import Dict, List
|
||||
import uuid
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
import yaml
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from pathlib import Path
|
||||
from platformdirs import PlatformDirs
|
||||
from pathlib import Path
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from db.models.base import Base
|
||||
import os
|
||||
|
||||
from db.models.media import MediaActor, MediaActorFile, MediaFile
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--file', '-f', help='file with links', default='~/.sync/media/list.txt')
|
||||
parser.add_argument('--video', help='store Url as VideoFile', action="store_true")
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--limit', '-l', type=int, help='maximum number of links to check')
|
||||
parser.add_argument('--dry-run', '-m', help='excute script without storing', action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
DB_USER: str = os.getenv("DB_USER", "kontor")
|
||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
||||
DB_SERVER: str = os.getenv("DB_SERVER", "127.0.0.1")
|
||||
DB_PORT: int = int(os.getenv("DB_PORT", 5432))
|
||||
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
||||
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
||||
|
||||
def get_logger(level, config: str):
|
||||
dirs = PlatformDirs(config)
|
||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
||||
with open(logging_config, 'rt') as f:
|
||||
log_config = yaml.safe_load(f.read())
|
||||
logging.config.dictConfig(log_config)
|
||||
logger = logging.getLogger('development')
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
case 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
case 2:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
case _:
|
||||
logger.setLevel(logging.INFO)
|
||||
return logger
|
||||
|
||||
def get_session() -> Session:
|
||||
engine = create_engine(DATABASE_URL)
|
||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
return SessionLocal()
|
||||
|
||||
def load_data(filename: str, log) -> List[str]:
|
||||
links: List[str] = []
|
||||
log.debug("load_data")
|
||||
import_file = Path(filename)
|
||||
if not import_file.exists():
|
||||
log.info(f"File {filename} does not exist. Do nothing.")
|
||||
raise FileNotFoundError()
|
||||
log.info("read txt file")
|
||||
with open(filename, 'r') as txt_file:
|
||||
while line := txt_file.readline():
|
||||
# log.info(line.rstrip())
|
||||
links.append(line.rstrip())
|
||||
return links
|
||||
|
||||
def get_actors_mapping(actor_list: List[MediaActor]) -> Dict[str, MediaActor]:
|
||||
mapping: Dict[str, MediaActor] = {}
|
||||
for actor in actor_list:
|
||||
mapping[str(actor.url)] = actor
|
||||
return mapping
|
||||
|
||||
def get_actornames_mapping(actor_list: List[MediaActor]) -> Dict[str, MediaActor]:
|
||||
mapping: Dict[str, MediaActor] = {}
|
||||
for actor in actor_list:
|
||||
mapping[str(actor.name)] = actor
|
||||
return mapping
|
||||
|
||||
def get_meta_info(media_file: MediaFile, log) -> List[str]:
|
||||
actor_links: List[str] = []
|
||||
try:
|
||||
r = requests.get(media_file.url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
error404 = soup.css.select_one('.error404-title')
|
||||
if error404 and error404.get_text() == "Video nicht gefunden":
|
||||
log.warning(f"{error404.get_text()}")
|
||||
media_file.url = None
|
||||
media_file.review = False
|
||||
return actor_links
|
||||
title_tag = soup.find('title')
|
||||
if title_tag:
|
||||
media_file.title = title_tag.get_text()
|
||||
media_file.review = False
|
||||
anchors = soup.find_all('a', attrs={'href': re.compile("^https://.*pornstars/.*")})
|
||||
for anchor in anchors:
|
||||
link_url = str(anchor.get("href")) # type: ignore
|
||||
if link_url.endswith('all/countries'):
|
||||
continue
|
||||
if link_url in actor_links:
|
||||
continue
|
||||
actor_links.append(link_url)
|
||||
except Exception as error:
|
||||
log.info(f"something went wrong: {error}")
|
||||
media_file.title = None
|
||||
media_file.review = True
|
||||
log.info(f"update MediaFile with MetaInfos to {repr(media_file)}")
|
||||
log.info(f"links({len(actor_links)}): {actor_links}")
|
||||
return actor_links
|
||||
|
||||
def get_actor_name(actor_url: str, log: logging.Logger) -> str | None:
|
||||
try:
|
||||
r = requests.get(actor_url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
titles = soup.find_all('h1')
|
||||
for title in titles:
|
||||
log.info(f"title: {title.get_text()}")
|
||||
return title.get_text()
|
||||
except Exception as error:
|
||||
log.warning(f"something went wrong: {error}")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose, "kontor")
|
||||
logger.info('kontor.add_links started')
|
||||
if args.limit:
|
||||
logger.warning(f"check the first {args.limit} links")
|
||||
session = get_session()
|
||||
links_index = 1
|
||||
with session as db:
|
||||
links = load_data(args.file, logger)
|
||||
for link in links:
|
||||
logger.debug(f"process {link}")
|
||||
media_files = db.query(MediaFile).filter(MediaFile.url == link).all()
|
||||
media_actors = db.query(MediaActor).all()
|
||||
actor_mapping = get_actors_mapping(media_actors)
|
||||
actorname_mapping = get_actornames_mapping(media_actors)
|
||||
if len(media_files) == 0:
|
||||
logger.info(f"MediaFile for link {link} not found")
|
||||
media_file = MediaFile()
|
||||
media_file.id = str(uuid.uuid4())
|
||||
media_file.created_date = datetime.now()
|
||||
media_file.last_modified_date = datetime.now()
|
||||
media_file.version = 0
|
||||
media_file.url = link
|
||||
media_file.review = True
|
||||
media_file.should_download = True
|
||||
media_file.path = None
|
||||
media_file.cloud_link = None
|
||||
media_file.file_name = None
|
||||
actor_urls: List[str] = get_meta_info(media_file, logger)
|
||||
if not args.dry_run:
|
||||
db.add(media_file)
|
||||
db.commit()
|
||||
db.refresh(media_file)
|
||||
for actor_url in actor_urls:
|
||||
if actor_url in actor_mapping:
|
||||
media_actor: MediaActor = actor_mapping[actor_url]
|
||||
# logger.info(f"create mapping for {repr(media_actor)}")
|
||||
media_actor_file = MediaActorFile()
|
||||
media_actor_file.id = str(uuid.uuid4())
|
||||
media_actor_file.created_date = datetime.now()
|
||||
media_actor_file.last_modified_date = datetime.now()
|
||||
media_actor_file.version = 0
|
||||
media_actor_file.media_file_id = media_file.id
|
||||
media_actor_file.media_actor_id = media_actor.id
|
||||
logger.info(f"create mapping with {media_actor_file}")
|
||||
if not args.dry_run:
|
||||
db.add(media_actor_file)
|
||||
db.commit()
|
||||
else:
|
||||
media_actor: MediaActor = None # type: ignore
|
||||
actor_name = get_actor_name(actor_url, logger)
|
||||
if actor_name in actorname_mapping:
|
||||
media_actor = actorname_mapping[actor_name]
|
||||
else:
|
||||
media_actor = MediaActor()
|
||||
media_actor.id = str(uuid.uuid4())
|
||||
media_actor.created_date = datetime.now()
|
||||
media_actor.last_modified_date = datetime.now()
|
||||
media_actor.version = 0
|
||||
media_actor.name = get_actor_name(actor_url, logger)
|
||||
media_actor.url = actor_url
|
||||
logger.info(f"update MediaActor with {repr(media_actor)}")
|
||||
if not args.dry_run:
|
||||
db.add(media_actor)
|
||||
db.commit()
|
||||
media_actor_file = MediaActorFile()
|
||||
media_actor_file.id = str(uuid.uuid4())
|
||||
media_actor_file.created_date = datetime.now()
|
||||
media_actor_file.last_modified_date = datetime.now()
|
||||
media_actor_file.version = 0
|
||||
media_actor_file.media_file_id = media_file.id
|
||||
media_actor_file.media_actor_id = media_actor.id
|
||||
logger.info(f"create mapping with {media_actor_file}")
|
||||
if not args.dry_run:
|
||||
db.add(media_actor_file)
|
||||
db.commit()
|
||||
else:
|
||||
for media_file in media_files:
|
||||
logger.debug(f"MediaFile with {media_file.id} is found")
|
||||
links_index += 1
|
||||
if args.limit and args.limit < links_index:
|
||||
break
|
||||
logger.info('kontor.add_link finished')
|
||||
+60
-76
@@ -1,37 +1,41 @@
|
||||
"""
|
||||
Setup database connections
|
||||
"""
|
||||
import sqlite3
|
||||
import mariadb
|
||||
|
||||
import logging.config
|
||||
from platformdirs import PlatformDirs
|
||||
import sqlite3
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
import psycopg2
|
||||
import requests
|
||||
import yaml
|
||||
from platformdirs import PlatformDirs
|
||||
|
||||
|
||||
def get_database_cursors(log, config: str):
|
||||
dirs = PlatformDirs(config)
|
||||
database_config = Path(dirs.user_config_dir, 'database-config.yaml')
|
||||
with open(database_config, 'rt') as f:
|
||||
database_config = Path(dirs.user_config_dir, "database-config.yaml")
|
||||
with open(database_config, "rt") as f:
|
||||
db_config = yaml.safe_load(f.read())
|
||||
sqlite_db = db_config["sqlite"]["file"]
|
||||
log.info('using SQLite3 database {}'.format(sqlite_db))
|
||||
sqlite_conn = sqlite3.connect(sqlite_db, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
|
||||
mariadb_conn = mariadb.connect(
|
||||
host=db_config['mariadb']['host'],
|
||||
port=db_config['mariadb']['port'],
|
||||
user=db_config['mariadb']['user'],
|
||||
password=db_config['mariadb']['password'],
|
||||
database=db_config['mariadb']['database']
|
||||
log.info("using SQLite3 database {}".format(sqlite_db))
|
||||
sqlite_conn = sqlite3.connect(
|
||||
sqlite_db, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
|
||||
)
|
||||
return sqlite_conn, mariadb_conn
|
||||
mariadb_conn = None
|
||||
postgres_conn = psycopg2.connect(
|
||||
f"host={db_config['postgres']['host']} port={db_config['postgres']['port']} user={db_config['postgres']['user']} password={db_config['postgres']['password']} dbname={db_config['postgres']['database']}"
|
||||
)
|
||||
return sqlite_conn, mariadb_conn, postgres_conn
|
||||
|
||||
|
||||
def create_tables(sqlite_conn, logger, recreate_db, scripts):
|
||||
logger.info('create_tables')
|
||||
logger.info("create_tables")
|
||||
for table_id in scripts:
|
||||
create_statement = scripts[table_id]['create']
|
||||
drop_statement = scripts[table_id]['drop']
|
||||
create_statement = scripts[table_id]["create"]
|
||||
drop_statement = scripts[table_id]["drop"]
|
||||
logger.debug(create_statement)
|
||||
cursor = sqlite_conn.cursor()
|
||||
if recreate_db:
|
||||
@@ -42,11 +46,12 @@ def create_tables(sqlite_conn, logger, recreate_db, scripts):
|
||||
|
||||
def get_logger(level, config: str):
|
||||
dirs = PlatformDirs(config)
|
||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
||||
with open(logging_config, 'rt') as f:
|
||||
logging_config = Path(dirs.user_config_dir, "logging-config.yaml")
|
||||
log_config = None
|
||||
with open(logging_config, "rt") as f:
|
||||
log_config = yaml.safe_load(f.read())
|
||||
logging.config.dictConfig(log_config)
|
||||
logger = logging.getLogger('development')
|
||||
logger = logging.getLogger("development")
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
@@ -60,59 +65,38 @@ def get_logger(level, config: str):
|
||||
return logger
|
||||
|
||||
|
||||
def get_meta_data(mariadb_conn):
|
||||
mariadb_cursor = mariadb_conn.cursor()
|
||||
select_statement = "SELECT id, table_name FROM meta_data_table"
|
||||
mariadb_cursor.execute(select_statement)
|
||||
rows = mariadb_cursor.fetchall()
|
||||
meta_data = {}
|
||||
for (identifier, table_name) in rows:
|
||||
table_data = {"name": table_name}
|
||||
mariadb_cursor.execute("SELECT column_name, column_sync_name, column_type, column_modifier, column_order FROM meta_data_column WHERE table_id=?", (identifier, ))
|
||||
column_rows = mariadb_cursor.fetchall()
|
||||
column_list = []
|
||||
for (column_name, column_sync_name, column_type, column_modifier, column_order) in column_rows:
|
||||
column_data = {"column_name": column_name, "column_sync_name": column_sync_name, "column_type": column_type,
|
||||
"column_modifier": column_modifier, "column_order": column_order}
|
||||
column_list.append(column_data)
|
||||
# logger.info(column_list)
|
||||
table_data["columns"] = column_list
|
||||
meta_data[identifier] = table_data
|
||||
return meta_data
|
||||
|
||||
|
||||
def get_scripts(meta_data, logger):
|
||||
scripts_map = {}
|
||||
for table_id in meta_data:
|
||||
table_scripts = {}
|
||||
m_columns = []
|
||||
s_columns = []
|
||||
columns = []
|
||||
for column_data in meta_data[table_id]["columns"]:
|
||||
column_line = "{} {}".format(column_data["column_sync_name"], column_data["column_type"])
|
||||
if column_data["column_modifier"]:
|
||||
column_line += " " + column_data["column_modifier"]
|
||||
columns.append(column_line)
|
||||
m_columns.append(column_data['column_name'])
|
||||
s_columns.append(column_data['column_sync_name'])
|
||||
table_name = meta_data[table_id]["name"]
|
||||
create_statement = "CREATE TABLE IF NOT EXISTS {} ({});".format(table_name, ", ".join(columns))
|
||||
drop_statement = 'DROP TABLE IF EXISTS {}'.format(table_name)
|
||||
select_mariadb_statement = 'SELECT {} FROM {}'.format(', '.join(m_columns), table_name)
|
||||
select_sqlite_statement = 'SELECT {} FROM {}'.format(', '.join(s_columns), table_name)
|
||||
insert_sqlite_statement = 'INSERT INTO {}({}) VALUES({})'.format(table_name, ', '.join(s_columns), ', '.join(['?']*len(s_columns)))
|
||||
insert_mariadb_statement = 'INSERT INTO {}({}) VALUES({})'.format(table_name, ', '.join(m_columns), ', '.join(['?']*len(m_columns)))
|
||||
truncate_mariadb_statement = 'TRUNCATE {}'.format(table_name)
|
||||
#logger.debug(create_statement)
|
||||
#logger.debug(select_mariadb_statement)
|
||||
table_scripts["create"] = create_statement
|
||||
table_scripts["drop"] = drop_statement
|
||||
table_scripts["select_mariadb"] = select_mariadb_statement
|
||||
table_scripts["select_sqlite"] = select_sqlite_statement
|
||||
table_scripts["insert_sqlite"] = insert_sqlite_statement
|
||||
table_scripts["insert_mariadb"] = insert_mariadb_statement
|
||||
table_scripts["truncate_mariadb"] = truncate_mariadb_statement
|
||||
table_scripts["count"] = "SELECT COUNT(*) FROM {}".format(table_name)
|
||||
table_scripts["name"] = table_name
|
||||
scripts_map[table_id] = table_scripts
|
||||
return scripts_map
|
||||
def get_api_config(log: Logger, config: str) -> Dict[str, Any]:
|
||||
api_data: Dict[str, Any] = {}
|
||||
token: str | None = None
|
||||
host: str | None = None
|
||||
port: int = 0
|
||||
dirs = PlatformDirs(config)
|
||||
api_config = Path(dirs.user_config_dir, "api.yaml")
|
||||
with open(api_config, "rt") as f:
|
||||
api_data = yaml.safe_load(f.read())
|
||||
if not api_data:
|
||||
log.fatal("API configuration is missing")
|
||||
return api_data
|
||||
host = api_data["host"]
|
||||
port = api_data["login_port"]
|
||||
if not token:
|
||||
log.info("Call login first")
|
||||
login_url = f"http://{host}:{port}/login"
|
||||
login_data = {}
|
||||
login_data["email"] = api_data["email"]
|
||||
login_data["password"] = api_data["password"]
|
||||
response = requests.post(login_url, json=login_data)
|
||||
status = response.status_code
|
||||
log.info(f"Status: {status}")
|
||||
if status != 200:
|
||||
log.fatal("authentication failed")
|
||||
return api_data
|
||||
data = response.json()
|
||||
log.debug(f"got data: {data}")
|
||||
token = data["access_token"]
|
||||
token_type = data["token_type"]
|
||||
api_data["token"] = token
|
||||
api_data["token_type"] = token_type
|
||||
with open(api_config, "w") as f:
|
||||
yaml.dump(api_data, f)
|
||||
return api_data
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from typing import Any, Dict
|
||||
from db.models.admin import (
|
||||
Assignment,
|
||||
Token,
|
||||
Profile,
|
||||
Permission,
|
||||
MailAccount,
|
||||
Mail,
|
||||
)
|
||||
from db.models.bookshelf import (
|
||||
ArticleAuthor,
|
||||
BookAuthor,
|
||||
BookshelfPublisher,
|
||||
Article,
|
||||
Book,
|
||||
Author,
|
||||
)
|
||||
from db.models.comic import (
|
||||
Issue,
|
||||
StoryArc,
|
||||
TradePaperback,
|
||||
Volume,
|
||||
ComicWork,
|
||||
IssueWork,
|
||||
Artist,
|
||||
Comic,
|
||||
Publisher,
|
||||
WorkType,
|
||||
)
|
||||
from db.models.media import (
|
||||
MediaFile,
|
||||
MediaActor,
|
||||
MediaActorFile,
|
||||
MediaArticle,
|
||||
MediaVideo,
|
||||
)
|
||||
from db.models.tysc import (
|
||||
Card,
|
||||
CardSet,
|
||||
Rooster,
|
||||
Team,
|
||||
FieldPosition,
|
||||
Player,
|
||||
Vendor,
|
||||
Sport,
|
||||
)
|
||||
|
||||
registry: Dict[str, Any] = {
|
||||
Sport.__tablename__: Sport,
|
||||
Player.__tablename__: Player,
|
||||
Team.__tablename__: Team,
|
||||
FieldPosition.__tablename__: FieldPosition,
|
||||
Rooster.__tablename__: Rooster,
|
||||
Vendor.__tablename__: Vendor,
|
||||
CardSet.__tablename__: CardSet,
|
||||
Card.__tablename__: Card,
|
||||
Artist.__tablename__: Artist,
|
||||
Publisher.__tablename__: Publisher,
|
||||
WorkType.__tablename__: WorkType,
|
||||
Comic.__tablename__: Comic,
|
||||
Volume.__tablename__: Volume,
|
||||
StoryArc.__tablename__: StoryArc,
|
||||
Issue.__tablename__: Issue,
|
||||
TradePaperback.__tablename__: TradePaperback,
|
||||
ComicWork.__tablename__: ComicWork,
|
||||
IssueWork.__tablename__: IssueWork,
|
||||
Article.__tablename__: Article,
|
||||
BookshelfPublisher.__tablename__: BookshelfPublisher,
|
||||
Book.__tablename__: Book,
|
||||
Author.__tablename__: Author,
|
||||
ArticleAuthor.__tablename__: ArticleAuthor,
|
||||
BookAuthor.__tablename__: BookAuthor,
|
||||
MediaArticle.__tablename__: MediaArticle,
|
||||
MediaVideo.__tablename__: MediaVideo,
|
||||
MediaFile.__tablename__: MediaFile,
|
||||
MediaActor.__tablename__: MediaActor,
|
||||
MediaActorFile.__tablename__: MediaActorFile,
|
||||
Profile.__tablename__: Profile,
|
||||
Permission.__tablename__: Permission,
|
||||
Assignment.__tablename__: Assignment,
|
||||
Token.__tablename__: Token,
|
||||
MailAccount.__tablename__: MailAccount,
|
||||
Mail.__tablename__: Mail
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Profile(Base, BaseMixin):
|
||||
__tablename__ = 'profile'
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
user_name = Column(String, nullable=False)
|
||||
email = Column(String)
|
||||
password = Column(String)
|
||||
enabled = Column(Boolean)
|
||||
assignments = relationship("Assignment")
|
||||
tokens = relationship("Token")
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
full_name: str = ""
|
||||
if self.first_name is not None:
|
||||
full_name += str(self.first_name)
|
||||
if self.last_name is not None:
|
||||
if len(full_name) > 0:
|
||||
full_name += " "
|
||||
full_name += str(self.last_name)
|
||||
return full_name
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.first_name = import_data['first_name']
|
||||
self.last_name = import_data['last_name']
|
||||
self.user_name = import_data['user_name']
|
||||
self.email = import_data['email']
|
||||
self.password = import_data['password']
|
||||
self.enabled = import_data['enabled']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['first_name'] = self.first_name
|
||||
item['last_name'] = self.last_name
|
||||
item['user_name'] = self.user_name
|
||||
item['email'] = self.email
|
||||
item['password'] = self.password
|
||||
item['enabled'] = self.enabled
|
||||
return item
|
||||
|
||||
|
||||
class Token(Base, BaseMixin):
|
||||
__tablename__ = "token"
|
||||
token = Column(String, nullable=False, unique=True)
|
||||
name = Column(String)
|
||||
last_used_date: Mapped[datetime] = mapped_column()
|
||||
enabled = Column(Boolean)
|
||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
||||
profile = relationship("Profile", back_populates="tokens")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.token = import_data['token']
|
||||
self.name = import_data['name']
|
||||
self.last_used_date = import_data['last_used_date']
|
||||
self.enabled = import_data['enabled']
|
||||
self.profile_id = import_data['profile_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['token'] = self.token
|
||||
item['name'] = self.name
|
||||
item['last_used_date'] = self.last_used_date
|
||||
item['enabled'] = self.enabled
|
||||
item['profile_id'] = self.profile_id
|
||||
return item
|
||||
|
||||
|
||||
class Permission(Base, BaseMixin):
|
||||
__tablename__ = "permission"
|
||||
name = Column(String, nullable=False)
|
||||
assignments = relationship("Assignment")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
return item
|
||||
|
||||
class Assignment(Base, BaseMixin):
|
||||
__tablename__ = "assignment"
|
||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
||||
profile = relationship("Profile", back_populates="assignments")
|
||||
permission_id = Column(String, ForeignKey("permission.id"), nullable=False)
|
||||
permission = relationship("Permission", back_populates="assignments")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.profile_id = import_data['profile_id']
|
||||
self.permission_id = import_data['permission_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['profile_id'] = self.profile_id
|
||||
item['permission_id'] = self.permission_id
|
||||
return item
|
||||
|
||||
|
||||
class MailAccount(Base, BaseMixin):
|
||||
__tablename__ = "mail_account"
|
||||
host = Column(String)
|
||||
port = Column(Integer)
|
||||
protocol = Column(String)
|
||||
user_name = Column(String)
|
||||
password = Column(String)
|
||||
start_tls = Column(Boolean)
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.host = import_data['host']
|
||||
self.port = import_data['port']
|
||||
self.protocol = import_data['protocol']
|
||||
self.user_name = import_data['user_name']
|
||||
self.password = import_data['password']
|
||||
self.start_tls = import_data['start_tls']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['host'] = self.host
|
||||
item['port'] = self.port
|
||||
item['protocol'] = self.protocol
|
||||
item['user_name'] = self.user_name
|
||||
item['password'] = self.password
|
||||
item['start_tls'] = self.start_tls
|
||||
return item
|
||||
|
||||
|
||||
class Mail(Base, BaseMixin):
|
||||
__tablename__ = "mail"
|
||||
folder: Mapped[str] = mapped_column()
|
||||
subject: Mapped[str] = mapped_column()
|
||||
body: Mapped[str] = mapped_column()
|
||||
sent_date: Mapped[datetime] = mapped_column()
|
||||
received_date: Mapped[datetime] = mapped_column()
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.folder = import_data['folder']
|
||||
self.subject = import_data['subject']
|
||||
self.body = import_data['body']
|
||||
self.sent_date = import_data['sent_date']
|
||||
self.received_date = import_data['received_date']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['folder'] = self.folder
|
||||
item['subject'] = self.subject
|
||||
item['body'] = self.body
|
||||
item['sent_date'] = str(self.sent_date)
|
||||
item['received_date'] = str(self.received_date)
|
||||
return item
|
||||
@@ -1,8 +1,7 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import func, Column, String
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy import func, Column, String, Boolean
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
|
||||
@@ -11,8 +10,8 @@ class Base(DeclarativeBase):
|
||||
|
||||
|
||||
class BaseMixin:
|
||||
id = Column(String(255), primary_key=True, default=uuid.uuid4())
|
||||
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
|
||||
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
# created_date = Column(DateTime)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
# last_modified_date = Column(DateTime)
|
||||
@@ -22,10 +21,10 @@ class BaseMixin:
|
||||
|
||||
|
||||
class BaseVideoMixin:
|
||||
cloud_link = Column(String(255))
|
||||
file_name = Column(String(255))
|
||||
path = Column(String(255))
|
||||
review = Column(BIT(1))
|
||||
title = Column(String(255))
|
||||
url = Column(String(255), unique=True)
|
||||
should_download = Column(BIT(1))
|
||||
cloud_link = Column(String, nullable=True)
|
||||
file_name = Column(String, nullable=True)
|
||||
path = Column(String)
|
||||
review = Column(Boolean)
|
||||
title = Column(String)
|
||||
url = Column(String, nullable=True)
|
||||
should_download = Column(Boolean)
|
||||
@@ -0,0 +1,159 @@
|
||||
from typing import Any, AnyStr, Dict
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Article(Base, BaseMixin):
|
||||
__tablename__ = 'article'
|
||||
title = Column(String, unique=True)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.title = import_data['title']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['title'] = self.title
|
||||
return item
|
||||
|
||||
|
||||
class Author(Base, BaseMixin):
|
||||
__tablename__ = 'author'
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.first_name = import_data['first_name']
|
||||
self.last_name = import_data['last_name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['first_name'] = self.first_name
|
||||
item['last_name'] = self.last_name
|
||||
return item
|
||||
|
||||
|
||||
class BookshelfPublisher(Base, BaseMixin):
|
||||
__tablename__ = 'bookshelf_publisher'
|
||||
name = Column(String, unique=True)
|
||||
books = relationship("Book")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
return item
|
||||
|
||||
|
||||
class Book(Base, BaseMixin):
|
||||
__tablename__ = 'book'
|
||||
isbn = Column(String, unique=True)
|
||||
title = Column(String)
|
||||
year = Column(Integer, nullable=False)
|
||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.isbn = import_data['isbn']
|
||||
self.title = import_data['title']
|
||||
self.year = import_data['year']
|
||||
self.publisher_id = import_data['publisher_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['isbn'] = self.isbn
|
||||
item['title'] = self.title
|
||||
item['year'] = self.year
|
||||
item['publisher_id'] = self.publisher_id
|
||||
return item
|
||||
|
||||
|
||||
class ArticleAuthor(Base, BaseMixin):
|
||||
__tablename__ = 'article_author'
|
||||
article_id = Column(String, ForeignKey('article.id'), nullable=False)
|
||||
article = relationship('Article', back_populates="article_authors")
|
||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
||||
author = relationship('Author', back_populates="article_authors")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.article_id = import_data['article_id']
|
||||
self.author_id = import_data['author_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['article_id'] = self.article_id
|
||||
item['author_id'] = self.author_id
|
||||
return item
|
||||
|
||||
|
||||
class BookAuthor(Base, BaseMixin):
|
||||
__tablename__ = 'book_author'
|
||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
||||
author = relationship('Author', back_populates="book_authors")
|
||||
book_id = Column(String, ForeignKey('book.id'), nullable=False)
|
||||
book = relationship('Book', back_populates="book_authors")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.author_id = import_data['author_id']
|
||||
self.book_id = import_data['book_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['author_id'] = self.author_id
|
||||
item['book_id'] = self.book_id
|
||||
return item
|
||||
@@ -0,0 +1,352 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import AnyStr, Dict, List, Optional, Any
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Publisher(Base):
|
||||
__tablename__ = "publisher"
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
version: Mapped[int] = mapped_column(default=0)
|
||||
name = Column(String, unique=True)
|
||||
weblink = Column(String, nullable=True)
|
||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
||||
comics = relationship("Comic")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Publisher({self.id} {self.name})'
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.parent_publisher_id = import_data['parent_publisher_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'name': self.name,
|
||||
'weblink': self.weblink,
|
||||
'parent_publisher_id': self.parent_publisher_id
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class Comic(Base, BaseMixin):
|
||||
__tablename__ = 'comic'
|
||||
title = Column(String, unique=True)
|
||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(Boolean)
|
||||
completed = Column(Boolean)
|
||||
weblink = Column(String, nullable=True)
|
||||
issues = relationship("Issue")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
volumes = relationship("Volume")
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title}({self.id})'
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.title = import_data['title']
|
||||
self.publisher_id = import_data['publisher_id']
|
||||
self.current_order = import_data['current_order']
|
||||
self.completed = import_data['completed']
|
||||
if 'weblink' in import_data:
|
||||
self.weblink = import_data['weblink']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'title': self.title,
|
||||
'publisher_id': self.publisher_id,
|
||||
'current_order': self.current_order,
|
||||
'completed': self.completed,
|
||||
'weblink': self.weblink
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class Volume(Base, BaseMixin):
|
||||
__tablename__ = "volume"
|
||||
name = Column(String, nullable=False)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="volumes")
|
||||
story_arcs = relationship("StoryArc")
|
||||
issues = relationship("Issue")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.comic_id = import_data['comic_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['comic_id'] = self.comic_id
|
||||
return item
|
||||
|
||||
class TradePaperback(Base, BaseMixin):
|
||||
__tablename__ = "trade_paperback"
|
||||
name = Column(String, nullable=False)
|
||||
issue_start = Column(Integer)
|
||||
issue_end = Column(Integer)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="trade_paperbacks")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.issue_start = import_data['issue_start']
|
||||
self.issue_end = import_data['issue_end']
|
||||
self.comic_id = import_data['comic_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['issue_start'] = self.issue_start
|
||||
item['issue_end'] = self.issue_end
|
||||
item['comic_id'] = self.comic_id
|
||||
return item
|
||||
|
||||
|
||||
class StoryArc(Base, BaseMixin):
|
||||
__tablename__ = "story_arc"
|
||||
name = Column(String, nullable=False)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="story_arcs")
|
||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||
volume = relationship("Volume", back_populates="story_arcs")
|
||||
issues = relationship("Issue")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.comic_id = import_data['comic_id']
|
||||
self.volume_id = import_data['volume_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'name': self.name,
|
||||
'comic_id': self.comic_id,
|
||||
'volume_id': self.volume_id
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class Issue(Base, BaseMixin):
|
||||
__tablename__ = "issue"
|
||||
issue_number = Column(String)
|
||||
title = Column(String, nullable=True)
|
||||
published_on: Mapped[datetime] = mapped_column(nullable=True)
|
||||
in_stock = Column(Boolean)
|
||||
is_read = Column(Boolean)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="issues")
|
||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||
volume = relationship("Volume", back_populates="issues")
|
||||
story_arc_id = Column(String, ForeignKey("story_arc.id"), nullable=True)
|
||||
story_arc = relationship("StoryArc", back_populates="issues")
|
||||
issue_works = relationship("IssueWork")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.issue_number = import_data['issue_number']
|
||||
self.title = import_data['title']
|
||||
if import_data['published_on'] == 'None':
|
||||
self.published_on = None # type: ignore
|
||||
else:
|
||||
self.published_on = import_data['published_on']
|
||||
self.in_stock = import_data['in_stock']
|
||||
self.is_read = import_data['is_read']
|
||||
self.comic_id = import_data['comic_id']
|
||||
self.volume_id = import_data['volume_id']
|
||||
self.story_arc_id = import_data['story_arc_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'issue_number': self.issue_number,
|
||||
'title': self.title,
|
||||
'published_on': str(self.published_on),
|
||||
'in_stock': self.in_stock,
|
||||
'is_read': self.is_read,
|
||||
'comic_id': self.comic_id,
|
||||
'volume_id': self.volume_id,
|
||||
'story_arc_id': self.story_arc_id
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class Artist(Base, BaseMixin):
|
||||
__tablename__ = "artist"
|
||||
name = Column(String, nullable=False)
|
||||
weblink = Column(String, nullable=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
issue_works = relationship("IssueWork")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
if 'weblink' in import_data:
|
||||
self.weblink = import_data['weblink']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'name': self.name,
|
||||
'weblink': self.weblink
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class WorkType(Base, BaseMixin):
|
||||
__tablename__ = "worktype"
|
||||
name = Column(String, nullable=False, unique=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
issue_works = relationship("IssueWork")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.id})'
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'name': self.name
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class ComicWork(Base, BaseMixin):
|
||||
__tablename__ = "comic_work"
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="comic_works")
|
||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
||||
artist = relationship("Artist", back_populates="comic_works")
|
||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||
work_type = relationship("WorkType", back_populates="comic_works")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.comic_id = import_data['comic_id']
|
||||
self.artist_id = import_data['artist_id']
|
||||
self.work_type_id = import_data['work_type_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'comic_id': self.comic_id,
|
||||
'artist_id': self.artist_id,
|
||||
'work_type_id': self.work_type_id
|
||||
}
|
||||
return item
|
||||
|
||||
|
||||
class IssueWork(Base, BaseMixin):
|
||||
__tablename__ = "issue_work"
|
||||
issue_id = Column(String, ForeignKey("issue.id"), nullable=False)
|
||||
issue = relationship("Issue", back_populates="issue_works")
|
||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
||||
artist = relationship("Artist", back_populates="issue_works")
|
||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||
work_type = relationship("WorkType", back_populates="issue_works")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.issue_id = import_data['issue_id']
|
||||
self.artist_id = import_data['artist_id']
|
||||
self.work_type_id = import_data['work_type_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {
|
||||
'id': self.id,
|
||||
'created_date': str(self.created_date),
|
||||
'last_modified_date': str(self.last_modified_date),
|
||||
'version': self.version,
|
||||
'issue_id': self.issue_id,
|
||||
'artist_id': self.artist_id,
|
||||
'work_type_id': self.work_type_id
|
||||
}
|
||||
return item
|
||||
@@ -0,0 +1,66 @@
|
||||
from enum import Enum, auto
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from db.models import registry
|
||||
|
||||
|
||||
class ColumnEntry(Enum):
|
||||
COLUMN_NAME = 'column'
|
||||
COLUMN_LABEL = 'label'
|
||||
COLUMN_ORDER = 'order'
|
||||
COLUMN_REF_COLUMN = 'ref_column'
|
||||
COLUMN_TYPE = 'type'
|
||||
COLUMN_WIDGET = 'widget'
|
||||
|
||||
|
||||
class StatusType(Enum):
|
||||
UNKNOWN = auto()
|
||||
FILE_NAME = auto()
|
||||
FILE_ID = auto()
|
||||
DUPLICATE = auto()
|
||||
CLOUD_LINK = auto()
|
||||
CLOUD_LINK_ID = auto()
|
||||
|
||||
|
||||
class ExportType(Enum):
|
||||
JSON = "JSON"
|
||||
YAML = "YAML"
|
||||
SQLITE = "SQLite"
|
||||
|
||||
|
||||
class KontorDB:
|
||||
|
||||
def __init__(self, db_engine: Any, log: Logger):
|
||||
self.engine = db_engine
|
||||
self.log = log
|
||||
|
||||
def data(self, table_name: str, columns: dict, filters: dict) -> list:
|
||||
data = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table = registry[table_name]
|
||||
with __session__() as session:
|
||||
entries = []
|
||||
if len(filters) == 0:
|
||||
entries = session.scalars(select(table)).all()
|
||||
else:
|
||||
entries = session.scalars(select(table).filter_by(**filters)).all()
|
||||
for entry in entries:
|
||||
# self.log.info("data: %s", entry)
|
||||
row = []
|
||||
for order in columns.keys():
|
||||
column_name = columns[order][ColumnEntry.COLUMN_NAME]
|
||||
ref_column = columns[order][ColumnEntry.COLUMN_REF_COLUMN]
|
||||
if str(column_name).endswith("_id"):
|
||||
ref_table = column_name[:-3]
|
||||
ref = getattr(entry, ref_table)
|
||||
value = getattr(ref, ref_column)
|
||||
row.append(value)
|
||||
else:
|
||||
row.append(getattr(entry, column_name))
|
||||
data.append(row)
|
||||
# self.log.info("data: %s", data)
|
||||
return data
|
||||
@@ -0,0 +1,224 @@
|
||||
import re
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from sqlalchemy import Boolean, Column, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from db.models.base import Base, BaseMixin, BaseVideoMixin
|
||||
|
||||
|
||||
class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
||||
__tablename__ = 'media_file'
|
||||
media_actor_files = relationship("MediaActorFile")
|
||||
|
||||
def __repr__(self):
|
||||
return f'MediaFile(\n\tID: {self.id}\n\tTitle: {self.title}\n\tURL: {self.url}\n\tReview: {self.review}\n\tDownload: {self.should_download}\n\tPath: {self.path}\n\tCloudlink: {self.cloud_link})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title}({self.id})'
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.cloud_link = import_data['cloud_link']
|
||||
self.file_name = import_data['file_name']
|
||||
self.path = import_data['path']
|
||||
self.review = import_data['review']
|
||||
self.title = import_data['title']
|
||||
self.url = import_data['url']
|
||||
self.should_download = import_data['should_download']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['cloud_link'] = self.cloud_link
|
||||
item['file_name'] = self.file_name
|
||||
item['path'] = self.path
|
||||
item['review'] = self.review
|
||||
item['title'] = self.title
|
||||
item['url'] = self.url
|
||||
item['should_download'] = self.should_download
|
||||
return item
|
||||
|
||||
def update_title(self) -> None:
|
||||
print(f"update title for {self.url}")
|
||||
try:
|
||||
r = requests.get(self.url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
title_tag = soup.find('title')
|
||||
if title_tag:
|
||||
self.title = title_tag.get_text()
|
||||
self.review = False
|
||||
except:
|
||||
self.title = None
|
||||
self.review = True
|
||||
self.last_modified_date = datetime.now()
|
||||
|
||||
def download_file(self, download_dir: str, dl_tool: str):
|
||||
print(f"download file for {self.url} to {download_dir}")
|
||||
result = subprocess.run([dl_tool, self.url], cwd=download_dir, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
output = result.stdout
|
||||
output = re.sub(' +', ' ', output)
|
||||
lines_list = output.splitlines()
|
||||
file_name = self.__parse_output__(lines_list)
|
||||
if file_name is None:
|
||||
self.review = True
|
||||
self.should_download = True
|
||||
self.file_name = None
|
||||
else:
|
||||
download_file = Path(file_name)
|
||||
self.should_download = False
|
||||
self.file_name = download_file.name
|
||||
self.cloud_link = str(download_file.absolute())
|
||||
self.last_modified_date = datetime.now()
|
||||
|
||||
def __parse_output__(self, lines_list):
|
||||
self.file_name = None
|
||||
for line in lines_list:
|
||||
if 'has already been downloaded' in line:
|
||||
end_len = len(' has already been downloaded')
|
||||
self.file_name = line[11:-end_len]
|
||||
if 'Destination' in line:
|
||||
line_len = len(line)
|
||||
start_len = len('[download] Destination: ')
|
||||
file_len = line_len - start_len
|
||||
self.file_name = line[-file_len:]
|
||||
return self.file_name
|
||||
|
||||
|
||||
class MediaActor(Base, BaseMixin):
|
||||
__tablename__ = 'media_actor'
|
||||
name = Column(String)
|
||||
url = Column(String, unique=True)
|
||||
media_actor_files = relationship("MediaActorFile")
|
||||
|
||||
def __repr__(self):
|
||||
return f'MediaActor(\n\tID: {self.id}\n\tName: {self.name}\n\tURL: {self.url})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.id})'
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.url = import_data['url']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['url'] = self.url
|
||||
return item
|
||||
|
||||
|
||||
class MediaActorFile(Base, BaseMixin):
|
||||
__tablename__ = 'media_actor_file'
|
||||
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
|
||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
||||
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||
|
||||
def __repr__(self):
|
||||
return f'MediaActorFile(\n\tID: {self.id}\n\tMediaActor: {self.media_actor_id}\n\tMediaFile: {self.media_file_id})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.id}: MediaActor: {self.media_actor_id} - MediaFile: {self.media_file_id}'
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.media_actor_id = import_data['media_actor_id']
|
||||
self.media_file_id = import_data['media_file_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['media_actor_id'] = self.media_actor_id
|
||||
item['media_file_id'] = self.media_file_id
|
||||
return item
|
||||
|
||||
class MediaArticle(Base, BaseMixin):
|
||||
__tablename__ = 'media_article'
|
||||
review = Column(Boolean)
|
||||
title = Column(String)
|
||||
url = Column(String, unique=True)
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.review = import_data['review']
|
||||
self.title = import_data['title']
|
||||
self.url = import_data['url']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['review'] = self.review
|
||||
item['title'] = self.title
|
||||
item['url'] = self.url
|
||||
return item
|
||||
|
||||
|
||||
class MediaVideo(Base, BaseMixin):
|
||||
__tablename__ = 'media_video'
|
||||
cloud_link = Column(String)
|
||||
file_name = Column(String)
|
||||
path = Column(String)
|
||||
review = Column(Boolean)
|
||||
title = Column(String)
|
||||
url = Column(String, unique=True)
|
||||
should_download = Column(Boolean)
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.cloud_link = import_data['cloud_link']
|
||||
self.file_name = import_data['file_name']
|
||||
self.path = import_data['path']
|
||||
self.review = import_data['review']
|
||||
self.title = import_data['title']
|
||||
self.url = import_data['url']
|
||||
self.should_download = import_data['should_download']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['cloud_link'] = self.cloud_link
|
||||
item['file_name'] = self.file_name
|
||||
item['path'] = self.path
|
||||
item['review'] = self.review
|
||||
item['title'] = self.title
|
||||
item['url'] = self.url
|
||||
item['should_download'] = self.should_download
|
||||
return item
|
||||
@@ -0,0 +1,259 @@
|
||||
from typing import Dict, Any
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Sport(Base, BaseMixin):
|
||||
__tablename__ = "sport"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name"),
|
||||
)
|
||||
name = Column(String, nullable=False, index=True, unique=True)
|
||||
teams = relationship("Team")
|
||||
positions = relationship("FieldPosition")
|
||||
|
||||
def __repr__(self):
|
||||
return f"Sport(id={self.id}, name={self.name}, created_date={self.created_date})"
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
return item
|
||||
|
||||
class Team(Base, BaseMixin):
|
||||
__tablename__ = "team"
|
||||
name = Column(String, nullable=False, index=True, unique=True)
|
||||
short_name = Column(String, nullable=False, )
|
||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||
sport = relationship("Sport", back_populates="teams")
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.short_name = import_data['short_name']
|
||||
self.sport_id = import_data['sport_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['short_name'] = self.short_name
|
||||
item['sport_id'] = self.sport_id
|
||||
return item
|
||||
|
||||
class FieldPosition(Base, BaseMixin):
|
||||
__tablename__ = "field_position"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "sport_id"),
|
||||
UniqueConstraint("short_name", "sport_id"),
|
||||
)
|
||||
name = Column(String, nullable=False, index=True)
|
||||
short_name = Column(String, nullable=False)
|
||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
||||
sport = relationship("Sport", back_populates="positions")
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.short_name = import_data['short_name']
|
||||
self.sport_id = import_data['sport_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['short_name'] = self.short_name
|
||||
item['sport_id'] = self.sport_id
|
||||
return item
|
||||
|
||||
|
||||
class Player(Base, BaseMixin):
|
||||
__tablename__ = "player"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("first_name", "last_name"),
|
||||
)
|
||||
first_name = Column(String, nullable=False, index=True)
|
||||
last_name = Column(String, nullable=False, index=True)
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
return f"{self.last_name}, {self.first_name}"
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.first_name = import_data['first_name']
|
||||
self.last_name = import_data['last_name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['first_name'] = self.first_name
|
||||
item['last_name'] = self.last_name
|
||||
return item
|
||||
|
||||
|
||||
class Rooster(Base, BaseMixin):
|
||||
__tablename__ = "rooster"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("year", "team_id", "player_id", "position_id"),
|
||||
)
|
||||
year = Column(Integer)
|
||||
team_id = Column(String, ForeignKey("team.id"), nullable=False, index=True)
|
||||
team = relationship("Team", back_populates="roosters")
|
||||
player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
|
||||
player = relationship("Player", back_populates="roosters")
|
||||
position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
|
||||
position = relationship("FieldPosition", back_populates="roosters")
|
||||
cards = relationship("Card")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.year = import_data['year']
|
||||
self.team_id = import_data['team_id']
|
||||
self.player_id = import_data['player_id']
|
||||
self.position_id = import_data['position_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['year'] = self.year
|
||||
item['team_id'] = self.team_id
|
||||
item['player_id'] = self.player_id
|
||||
item['position_id'] = self.position_id
|
||||
return item
|
||||
|
||||
|
||||
class Vendor(Base, BaseMixin):
|
||||
__tablename__ = "vendor"
|
||||
name = Column(String, nullable=False, unique=True, index=True)
|
||||
card_sets = relationship("CardSet")
|
||||
cards = relationship("Card")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
return item
|
||||
|
||||
|
||||
class CardSet(Base, BaseMixin):
|
||||
__tablename__ = "card_set"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "vendor_id"),
|
||||
)
|
||||
name = Column(String, index=True)
|
||||
parallel_set = Column(Boolean)
|
||||
insert_set = Column(Boolean)
|
||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||
vendor = relationship("Vendor", back_populates="card_sets")
|
||||
cards = relationship("Card")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.name = import_data['name']
|
||||
self.parallel_set = import_data['parallel_set']
|
||||
self.insert_set = import_data['insert_set']
|
||||
self.vendor_id = import_data['vendor_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['name'] = self.name
|
||||
item['parallel_set'] = self.parallel_set
|
||||
item['insert_set'] = self.insert_set
|
||||
item['vendor_id'] = self.vendor_id
|
||||
return item
|
||||
|
||||
|
||||
class Card(Base, BaseMixin):
|
||||
__tablename__ = "card"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("card_number", "year", "vendor_id", "card_set_id"),
|
||||
)
|
||||
card_number = Column(Integer, index=True)
|
||||
year = Column(Integer, index=True)
|
||||
card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
|
||||
card_set = relationship("CardSet", back_populates="cards")
|
||||
rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
|
||||
rooster = relationship("Rooster", back_populates="cards")
|
||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
|
||||
vendor = relationship("Vendor", back_populates="cards")
|
||||
|
||||
def import_dict(self, import_data: Dict[str, Any]):
|
||||
self.id = import_data['id']
|
||||
self.created_date = import_data['created_date']
|
||||
self.last_modified_date = import_data['last_modified_date']
|
||||
self.version = import_data['version']
|
||||
self.card_number = import_data['card_number']
|
||||
self.year = import_data['year']
|
||||
self.card_set_id = import_data['card_set_id']
|
||||
self.rooster_id = import_data['rooster_id']
|
||||
self.vendor_id = import_data['vendor_id']
|
||||
|
||||
def export_dict(self) -> Dict[str, Any]:
|
||||
item: Dict[str, Any] = {}
|
||||
item['id'] = self.id
|
||||
item['created_date'] = str(self.created_date)
|
||||
item['last_modified_date'] = str(self.last_modified_date)
|
||||
item['version'] = self.version
|
||||
item['card_number'] = self.card_number
|
||||
item['year'] = self.year
|
||||
item['card_set_id'] = self.card_set_id
|
||||
item['rooster_id'] = self.rooster_id
|
||||
item['vendor_id'] = self.vendor_id
|
||||
return item
|
||||
+97
-43
@@ -1,122 +1,176 @@
|
||||
"""
|
||||
download files with URLs from DB
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
||||
from datetime import datetime
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
from uuid import UUID
|
||||
|
||||
from platformdirs import PlatformDirs
|
||||
import requests
|
||||
from config import get_logger
|
||||
import yaml
|
||||
|
||||
from config import get_api_config, get_logger
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--dir', '-d', default='/data/media')
|
||||
parser.add_argument('--tool', '-t', default='yt-dlp')
|
||||
parser.add_argument('--dry-run', '-m', action='store_true')
|
||||
parser.add_argument("--verbose", "-v", action="count", default=0)
|
||||
parser.add_argument("--config", "-c", default="kontor-docker")
|
||||
parser.add_argument("--dir", "-d", default="/data/media")
|
||||
parser.add_argument("--limit", "-l", type=int, help="maximum number of links to check")
|
||||
parser.add_argument("--tool", "-t", default="yt-dlp")
|
||||
parser.add_argument("--dry-run", "-m", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
class FileStatus(Enum):
|
||||
DOWNLOADED = auto()
|
||||
RENAMED = auto()
|
||||
UNKNOWN = auto()
|
||||
|
||||
def download_file(url: str, file_info: dict, download_dir: str = "/data/media", dl_tool: str = "yt-dlp") -> dict:
|
||||
|
||||
def download_file(
|
||||
url: str,
|
||||
file_info: dict,
|
||||
download_dir: str = "/data/media",
|
||||
dl_tool: str = "yt-dlp",
|
||||
) -> dict:
|
||||
print(f"download file for {url} to {download_dir}")
|
||||
result = subprocess.run([dl_tool, url], cwd=download_dir, capture_output=True, text=True)
|
||||
result = subprocess.run(
|
||||
[dl_tool, url], cwd=download_dir, capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
output = result.stdout
|
||||
output = re.sub(' +', ' ', output)
|
||||
output = re.sub(" +", " ", output)
|
||||
lines_list = output.splitlines()
|
||||
file_name = __parse_output__(lines_list)
|
||||
if file_name is None:
|
||||
file_info['review'] = True
|
||||
file_info['should_download'] = True
|
||||
file_info['file_name'] = None
|
||||
log.info(f"found file: {file_name}")
|
||||
if file_name is None or not file_name.strip():
|
||||
file_info["review"] = True
|
||||
file_info["should_download"] = True
|
||||
file_info["file_name"] = None
|
||||
else:
|
||||
download_file_name = Path(download_dir, file_name)
|
||||
file_info['should_download'] = False
|
||||
file_info['file_name'] = download_file_name.name
|
||||
file_info['cloud_link'] = str(download_file_name.absolute())
|
||||
file_info['last_modified_date'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
file_info["should_download"] = False
|
||||
file_info["review"] = False
|
||||
file_info["file_name"] = download_file_name.name
|
||||
file_info["cloud_link"] = str(download_file_name.absolute())
|
||||
file_info["last_modified_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
return file_info
|
||||
|
||||
|
||||
def __parse_output__(lines_list: list[str]) -> str | None:
|
||||
file_name = None
|
||||
for line in lines_list:
|
||||
if 'has already been downloaded' in line:
|
||||
end_len = len(' has already been downloaded')
|
||||
log.debug(f"parse line: {line}")
|
||||
if "has already been downloaded" in line:
|
||||
end_len = len(" has already been downloaded")
|
||||
file_name = line[11:-end_len]
|
||||
if 'Destination' in line:
|
||||
log.info(f"file_name: {file_name}")
|
||||
break
|
||||
if "Destination" in line:
|
||||
line_len = len(line)
|
||||
start_len = len('[download] Destination: ')
|
||||
start_len = len("[download] Destination: ")
|
||||
file_len = line_len - start_len
|
||||
file_name = line[-file_len:]
|
||||
break
|
||||
else:
|
||||
file_name = None
|
||||
return file_name
|
||||
|
||||
|
||||
def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
||||
file_name_as_title = f"{media_file['file_name']}"
|
||||
if not file_name_as_title:
|
||||
log.info("title has not been set - start download")
|
||||
return FileStatus.UNKNOWN
|
||||
file_title = Path(dir, f"{file_name_as_title}.mp4")
|
||||
if file_title.exists():
|
||||
log.info(f"{file_name_as_title} has been downloaded")
|
||||
media_file['should_download'] = False
|
||||
media_file["should_download"] = False
|
||||
return FileStatus.DOWNLOADED
|
||||
file_name_as_id = f"{media_file['id']}"
|
||||
file_with_id_as_name = Path(dir, f"{file_name_as_id}.mp4")
|
||||
if file_with_id_as_name.exists():
|
||||
log.info(f"{file_with_id_as_name} has been downloaded and renamed")
|
||||
media_file['cloud_link'] = file_with_id_as_name
|
||||
media_file['should_download'] = False
|
||||
media_file["cloud_link"] = str(file_with_id_as_name)
|
||||
media_file["should_download"] = False
|
||||
return FileStatus.RENAMED
|
||||
log.info("could not find file - start download")
|
||||
return FileStatus.UNKNOWN
|
||||
|
||||
|
||||
def update_status(item_id: UUID, file_info: dict):
|
||||
update = requests.put(f"http://127.0.0.1:8800/media/files/{item_id}", json=file_info)
|
||||
def update_status(item_id: UUID, file_info: dict, api_data: Dict[str, Any]):
|
||||
host = api_data["host"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/files/{item_id}"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
update = requests.put(url, headers=headers, json=file_info)
|
||||
log.info(f"update status: {update.status_code}")
|
||||
log.info(f"update result: {update.json()}")
|
||||
|
||||
|
||||
def rename_file(file_info: dict):
|
||||
item_id = file_info['id']
|
||||
file = Path(args.dir, file_info['file_name'])
|
||||
item_id = file_info["id"]
|
||||
file_name = file_info["file_name"]
|
||||
if file_name is None or not file_name.strip():
|
||||
log.info("file_name is not set, rename is not executed")
|
||||
file_info["review"] = True
|
||||
file_info["should_download"] = True
|
||||
return
|
||||
file = Path(args.dir, file_name)
|
||||
new_file_path = file.with_name(f"{item_id}{file.suffix}")
|
||||
log.info(f"rename {file} to {new_file_path}")
|
||||
file.rename(Path(new_file_path))
|
||||
file_info['cloud_link'] = str(new_file_path)
|
||||
file_info["cloud_link"] = str(new_file_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info('kontor.download started')
|
||||
response = requests.get("http://127.0.0.1:8800/media/files?download=true")
|
||||
log.info("kontor.download started")
|
||||
api_data = get_api_config(log, args.config)
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/files?download=true"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(url, headers=headers)
|
||||
log.info(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
log.info(f"data: {len(data)}")
|
||||
entries_count = len(data)
|
||||
log.info(f"data: {entries_count}")
|
||||
mediafile_index = 1
|
||||
log.debug(f"data: {data}")
|
||||
missing_actors = {}
|
||||
if args.dry_run:
|
||||
sys.exit(0)
|
||||
if args.limit:
|
||||
log.warning(f"check the first {args.limit} links")
|
||||
for item in data:
|
||||
link = item['url']
|
||||
file_id = item['id']
|
||||
link = item["url"]
|
||||
file_id = item["id"]
|
||||
log.info(f"{file_id} - {link}")
|
||||
download_status: FileStatus = is_file_downloaded(item, args.dir)
|
||||
match download_status:
|
||||
case FileStatus.DOWNLOADED:
|
||||
rename_file(item)
|
||||
update_status(file_id, item)
|
||||
update_status(file_id, item, api_data)
|
||||
case FileStatus.RENAMED:
|
||||
log.info("update status")
|
||||
update_status(file_id, item)
|
||||
update_status(file_id, item, api_data)
|
||||
case FileStatus.UNKNOWN:
|
||||
download_file(link, item)
|
||||
download_file(link, item, args.dir)
|
||||
rename_file(item)
|
||||
log.info(f'{item}')
|
||||
update_status(file_id, item)
|
||||
log.info('kontor.download finished')
|
||||
|
||||
log.info(f"{item}")
|
||||
update_status(file_id, item, api_data)
|
||||
log.warning(f"processed {mediafile_index}/{entries_count}")
|
||||
if args.limit and args.limit <= mediafile_index:
|
||||
break
|
||||
mediafile_index += 1
|
||||
log.info("kontor.download finished")
|
||||
|
||||
+35
-23
@@ -2,17 +2,13 @@
|
||||
import data from json file to MariaDB
|
||||
"""
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
|
||||
import yaml
|
||||
import json
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from platformdirs import PlatformDirs
|
||||
from pathlib import Path
|
||||
|
||||
from schema.base import Base
|
||||
from schema.database import KontorDB
|
||||
from db.models import registry
|
||||
from db.models.base import Base
|
||||
from config import get_logger
|
||||
from schema.database import ExportType
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
@@ -20,24 +16,40 @@ parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--file', '-f', default='data.json')
|
||||
args = parser.parse_args()
|
||||
|
||||
DB_USER: str = os.getenv("DB_USER", "kontor")
|
||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
||||
DB_SERVER: str = os.getenv("DB_SERVER", "127.0.0.1")
|
||||
DB_PORT: int = int(os.getenv("DB_PORT", 5432))
|
||||
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
||||
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose, args.config)
|
||||
logger.info('kontor.export started')
|
||||
dirs = PlatformDirs(args.config)
|
||||
database_config = Path(dirs.user_config_dir, 'database-config.yaml')
|
||||
with open(database_config, 'rt') as f:
|
||||
db_config = yaml.safe_load(f.read())
|
||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
||||
db_config['mariadb']['user'],
|
||||
db_config['mariadb']['password'],
|
||||
db_config['mariadb']['host'],
|
||||
db_config['mariadb']['port'],
|
||||
db_config['mariadb']['database']
|
||||
))
|
||||
engine = create_engine(connect_string)
|
||||
engine = create_engine(DATABASE_URL)
|
||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||
__session__ = sessionmaker(bind=engine)
|
||||
kontor_db = KontorDB(engine, logger)
|
||||
kontor_db.export_db(ExportType.JSON, args.file)
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
with SessionLocal() as db:
|
||||
data = {}
|
||||
tables = registry.keys()
|
||||
for table in tables:
|
||||
# logger.info(f"Table {table.name} with {table.id}")
|
||||
model = registry[table]
|
||||
rows = db.query(model).all()
|
||||
entries = []
|
||||
for row in rows:
|
||||
entry = row.export_dict()
|
||||
entries.append(entry)
|
||||
data[table] = entries
|
||||
logger.info(f"{table}: {len(entries)} exported")
|
||||
try:
|
||||
json_dump = json.dumps(data, indent=4)
|
||||
with open(args.file, "w") as dump_file:
|
||||
dump_file.write(json_dump)
|
||||
except TypeError as error:
|
||||
logger.info(f"{error}")
|
||||
logger.info(f"{len(data)} tables exported")
|
||||
#kontor_db = KontorDB(engine, logger)
|
||||
#kontor_db.export_db(ExportType.JSON, args.file)
|
||||
logger.info('kontor.export finished')
|
||||
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
download files with URLs from DB
|
||||
"""
|
||||
import logging.config
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
import requests
|
||||
import re
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from config import get_api_config
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument("--config", "-c", default="kontor-docker")
|
||||
parser.add_argument('--all', '-a', action='store_true')
|
||||
parser.add_argument('--limit', '-l', type=int, help='maximum number of links to check')
|
||||
parser.add_argument('--add-actor', action='store_true', help='add missing actors')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
def get_logger(level: int) -> logging.Logger:
|
||||
logging.config.dictConfig({
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': logging.StreamHandler,
|
||||
'level': logging.DEBUG,
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stdout'
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'urllib3.connectionpool': {
|
||||
'level': 'WARNING',
|
||||
'propagate': False,
|
||||
},
|
||||
'root': {
|
||||
'level': 'DEBUG',
|
||||
'handlers': ['console'],
|
||||
},
|
||||
},
|
||||
})
|
||||
logger = logging.getLogger(__file__)
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
logger.setLevel(logging.WARNING)
|
||||
case 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
case 2:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
case _:
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
return logger
|
||||
|
||||
def update_file(log: logging.Logger, media_file, api_data: Dict[str, Any]):
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
url = f"http://{host}:{port}/api/media/files/{media_file['id']}"
|
||||
update = requests.put(url, headers=headers, json=media_file)
|
||||
log.debug(f"update status: {update.status_code}")
|
||||
log.debug(f"update result: {update.json()}")
|
||||
|
||||
def get_actor_links(log: logging.Logger, media_file_url: str, api_data: Dict[str, Any]) -> list[str]:
|
||||
try:
|
||||
r = requests.get(media_file_url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
error404 = soup.css.select_one('.error404-title')
|
||||
if error404 and error404.get_text() == "Video nicht gefunden":
|
||||
log.warning(f"{error404.get_text()}")
|
||||
media_file['url'] = None
|
||||
media_file['review'] = False
|
||||
update_file(log, media_file, api_data=api_data)
|
||||
return []
|
||||
anchors = soup.find_all('a', attrs={'href': re.compile("^https://.*pornstars/.*")})
|
||||
actor_links = []
|
||||
for anchor in anchors:
|
||||
link_url = str(anchor.get("href")) # type: ignore
|
||||
if link_url.endswith('all/countries'):
|
||||
continue
|
||||
if link_url in actor_links:
|
||||
continue
|
||||
actor_links.append(link_url)
|
||||
log.debug(f"links({len(actor_links)}): {actor_links}")
|
||||
return actor_links
|
||||
except Exception as error:
|
||||
log.warning(f"something went wrong: {error}")
|
||||
return []
|
||||
|
||||
def get_media_files(all_files: bool, api_data: Dict[str, Any])-> Any:
|
||||
files_url = ""
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
if all_files:
|
||||
files_url= f"http://{host}:{port}/api/media/files"
|
||||
else:
|
||||
files_url = f"http://{host}:{port}/api/media/files?review=true"
|
||||
response = requests.get(files_url, headers=headers)
|
||||
log.debug(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
return data
|
||||
|
||||
def update_media_file(item, log: logging.Logger, api_data: Dict[str, Any]) -> Any:
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/files/{item['id']}"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
update = requests.put(url, headers=headers, json=item)
|
||||
log.debug(f"update status: {update.status_code}")
|
||||
log.debug(f"update result: {update.json()}")
|
||||
return update.json()
|
||||
|
||||
def update_media_file_actors(mediafile: dict,
|
||||
actor_id_list: list[dict[str, str]],
|
||||
actor_links: list[str],
|
||||
map_ids_actor: dict[str, str],
|
||||
log: logging.Logger,
|
||||
api_data: Dict[str, Any]):
|
||||
media_file_id = mediafile['id']
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/files/{media_file_id}/actors"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
actor_response = requests.put(url, headers=headers, json=actor_id_list)
|
||||
files_actor_list = actor_response.json()
|
||||
persisted_actor_links_count: int = len(files_actor_list)
|
||||
found_actor_links_count: int = len(actor_links)
|
||||
if persisted_actor_links_count < found_actor_links_count:
|
||||
log.warning(f"{persisted_actor_links_count} links persisted, but {found_actor_links_count} links are available")
|
||||
mediafile['review'] = True
|
||||
elif persisted_actor_links_count > found_actor_links_count:
|
||||
log.warning("more persisted links than found actors")
|
||||
for file_actor in files_actor_list:
|
||||
actor_id = file_actor['actor_id']
|
||||
actor_url = map_ids_actor[actor_id]['url'] # type: ignore
|
||||
log.debug(f"check if actor({actor_id}) with {actor_url} in list")
|
||||
if actor_url not in actor_links:
|
||||
log.info(f"actor not found in links, delete relation {file_actor['id']}")
|
||||
delete_media_file_actor(file_actor['id'], log, api_data)
|
||||
mediafile['review'] = True
|
||||
else:
|
||||
mediafile['review'] = False
|
||||
log.debug(f"found {persisted_actor_links_count} actors")
|
||||
log.debug(f"found actors: {files_actor_list}")
|
||||
|
||||
def delete_media_file_actor(media_actor_file_id: str, log: logging.Logger, api_data: Dict[str, Any]):
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/actorfiles/{media_actor_file_id}"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
delete_response = requests.delete(url, headers=headers)
|
||||
if delete_response.status_code == 204:
|
||||
log.info(f"actor file relation with id {media_actor_file_id} successfully deleted")
|
||||
|
||||
def get_actor_ids(link_list: list[str],
|
||||
map_url_actor: dict[str, str],
|
||||
map_ids_actor: dict[str, str],
|
||||
map_path_actor: dict[str, str],
|
||||
missing_actors: dict[str, int],
|
||||
log: logging.Logger) -> list[dict[str, str]]:
|
||||
found_actors: list[dict[str, str]] = []
|
||||
for link in link_list:
|
||||
actor = get_persisted_actor(link, map_url_actor, map_ids_actor, map_path_actor, log)
|
||||
if actor:
|
||||
found_actors.append(actor)
|
||||
else:
|
||||
if link in missing_actors:
|
||||
count = missing_actors[link]
|
||||
missing_actors[link] = count +1
|
||||
else:
|
||||
missing_actors.update({link: 1})
|
||||
return found_actors
|
||||
|
||||
def get_persisted_actor(actor_url: str,
|
||||
map_url_actor: dict[str, str],
|
||||
map_ids_actor: dict[str, str],
|
||||
map_path_actor: dict[str, str],
|
||||
log: logging.Logger) -> dict[str, str] | None:
|
||||
alternate_url_actor: dict[str, dict[str, str]] = {
|
||||
'https://ge.xhamster2.com/pornstars/jean-yves-lecastel':
|
||||
{'id': 'e354b866-717c-4a66-ad38-bc7c23d97e36', 'name': 'Jean-Yves Le Castel', 'url': 'https://ge.xhamster.com/pornstars/jean-yves-le-castel'},
|
||||
'https://ge.xhamster.com/pornstars/jean-yves-lecastel':
|
||||
{'id': 'e354b866-717c-4a66-ad38-bc7c23d97e36', 'name': 'Jean-Yves Le Castel', 'url': 'https://ge.xhamster.com/pornstars/jean-yves-le-castel'},
|
||||
'https://ge.xhamster.com/pornstars/gracie-green':
|
||||
{'id': 'cbec2e0d-869c-40f1-923f-21958d938d9f', 'name': 'Gracie May Green', 'url':'https://ge.xhamster.com/pornstars/gracie-may-green'},
|
||||
'https://ge.xhamster.com/pornstars/thomas-hyka':
|
||||
{'id': '1d814b45-ea98-4acc-88a2-227d3ed36959', 'name': 'Thomas Crown', 'url':'https://ge.xhamster.com/pornstars/thomas-crown'},
|
||||
'https://ge.xhamster.com/pornstars/chloe-couture':
|
||||
{'id': 'e22003a5-60a9-4d86-a1df-ae09ecbe5200', 'name': 'Chloe Cherry', 'url':'https://ge.xhamster.com/pornstars/chloe-cherry'},
|
||||
'https://ge.xhamster.com/pornstars/dava-fox':
|
||||
{'id': 'd913b778-4507-421b-88e0-9da73bb80a63', 'name': 'Dava Foxx', 'url':'https://ge.xhamster.com/pornstars/dava-foxx'},
|
||||
'https://ge.xhamster.com/pornstars/john-dough':
|
||||
{'id': 'a2ecd50f-09b2-4d31-9fcf-1a1438700f51', 'name': 'Jon Dough', 'url':'https://ge.xhamster.com/pornstars/jon-dough'},
|
||||
'https://ge.xhamster.com/pornstars/erica-mori':
|
||||
{'id': '5379dab9-63da-44ed-baf1-929d74ac60b1', 'name': 'Polly Yangs', 'url':'https://ge.xhamster.com/pornstars/polly-yangs'},
|
||||
'https://ge.xhamster.com/pornstars/elnara-cat':
|
||||
{'id': '543952d7-59a9-4492-a70f-e384b5f8eb57', 'name': 'Renata Fox', 'url':'https://ge.xhamster.com/pornstars/renata-fox'},
|
||||
'https://ge.xhamster.com/pornstars/melissa-grand':
|
||||
{'id': '5d025bea-4af6-4197-b38d-3b3afa9d30b9', 'name': 'Melissa Benz', 'url':'https://ge.xhamster.com/pornstars/melissa-benz'},
|
||||
'https://ge.xhamster.com/pornstars/sindy-dollar':
|
||||
{'id': 'fa97769c-9e53-4613-b3c3-4cc1a2672d4b', 'name': 'Cindy Dollar', 'url':'https://ge.xhamster.com/pornstars/cindy-dollar'},
|
||||
} # type: ignore
|
||||
if actor_url in map_url_actor:
|
||||
actor_id: str = map_url_actor[actor_url]['id'] # type: ignore
|
||||
log.debug(f"found actor with id: {actor_id}")
|
||||
return map_ids_actor[actor_id] # type: ignore
|
||||
path = actor_url.split('/')[-1]
|
||||
if path in map_path_actor:
|
||||
actor_id: str = map_path_actor[path]['id'] # type: ignore
|
||||
log.debug(f"found actor with id: {actor_id} by path {path}")
|
||||
return map_ids_actor[actor_id] # type: ignore
|
||||
if actor_url in alternate_url_actor:
|
||||
actor_id: str = alternate_url_actor[actor_url]['id']
|
||||
log.info(f"found actor with id: {actor_id} by alternative {path}")
|
||||
return alternate_url_actor[actor_url]
|
||||
log.info(f"found actor {actor_url} missing")
|
||||
return None
|
||||
|
||||
def get_actors(log: logging.Logger, api_data: Dict[str, Any]):
|
||||
actors_url = {}
|
||||
actors_id = {}
|
||||
actors_path = {}
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/actors"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(url, headers=headers)
|
||||
data = response.json()
|
||||
for media_actor in data:
|
||||
actor_id = media_actor['id']
|
||||
actor_name = media_actor['name']
|
||||
actor_url = media_actor['url']
|
||||
actor = {}
|
||||
actor['id'] = actor_id
|
||||
actor['name'] = actor_name
|
||||
actor['url'] = actor_url
|
||||
actors_url[actor_url] = actor
|
||||
actors_id[actor_id] = actor
|
||||
actors_path[actor_url.split('/')[-1]] = actor
|
||||
log.debug(f'all actors: {actors_url}')
|
||||
log.debug(f'all actors: {actors_path}')
|
||||
return (actors_url, actors_id, actors_path)
|
||||
|
||||
def get_actor_name(actor_url: str, log: logging.Logger) -> str | None:
|
||||
try:
|
||||
r = requests.get(actor_url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
titles = soup.find_all('h1')
|
||||
for title in titles:
|
||||
log.info(f"title: {title.get_text()}")
|
||||
return title.get_text()
|
||||
except Exception as error:
|
||||
log.warning(f"something went wrong: {error}")
|
||||
return None
|
||||
|
||||
def create_actor(actor_url: str, actor_name: str, log: logging.Logger, api_data: Dict[str, Any]):
|
||||
new_actor = { 'name': actor_name, 'url': actor_url}
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url: str = f"http://{host}:{port}/api/media/actors"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
actor_response = requests.post(url, headers=headers, json=new_actor)
|
||||
log.warning(f"add status: {actor_response.status_code}")
|
||||
if actor_response.status_code == 201:
|
||||
actor_data = actor_response.json()
|
||||
log.warning(f"Actor {actor_data} persisted")
|
||||
else:
|
||||
log.info(f"Actor with {actor_url} not persisted")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose)
|
||||
log.warning('kontor.find_links started')
|
||||
log.debug('get all actors')
|
||||
api_data = get_api_config(log, args.config)
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
|
||||
(actors_url, actors_id, actors_path) = get_actors(log, api_data=api_data)
|
||||
data = get_media_files(args.all, api_data)
|
||||
entries_count = len(data)
|
||||
mediafile_index = 1
|
||||
log.debug(f"data: {len(data)}")
|
||||
missing_actors = {}
|
||||
if args.limit:
|
||||
log.warning(f"check the first {args.limit} links")
|
||||
for media_file in data:
|
||||
link = media_file['url']
|
||||
media_file_id = media_file['id']
|
||||
if not link:
|
||||
continue
|
||||
if str(link) == "None":
|
||||
continue
|
||||
log.warning(f"{media_file['id']} - {str(link)}")
|
||||
actor_links: list[str] = get_actor_links(log, link, api_data=api_data)
|
||||
actor_id_list = get_actor_ids(actor_links, actors_url, actors_id, actors_path, missing_actors, log)
|
||||
update_media_file_actors(media_file, actor_id_list, actor_links, actors_id, log, api_data=api_data)
|
||||
result = update_media_file(media_file, log, api_data=api_data)
|
||||
log.warning(f"processed {mediafile_index}/{entries_count}")
|
||||
if args.limit and args.limit <= mediafile_index:
|
||||
break
|
||||
mediafile_index += 1
|
||||
for link in missing_actors:
|
||||
log.info(f"{link}: {missing_actors[link]}")
|
||||
actor_name = get_actor_name(link, log)
|
||||
if actor_name and args.add_actor:
|
||||
create_actor(link, actor_name, log, api_data=api_data)
|
||||
log.info("Sort missing actors by occurence count:")
|
||||
sorted_missing = dict(sorted(missing_actors.items(), key=lambda item: item[1]))
|
||||
for key in sorted_missing:
|
||||
log.info(f"{key} : {sorted_missing[key]}")
|
||||
log.warning('kontor.find_links finished')
|
||||
+160
-26
@@ -1,42 +1,176 @@
|
||||
"""
|
||||
import data from json file to MariaDB
|
||||
import data from json file to PostgreSQL
|
||||
"""
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
|
||||
import yaml
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from platformdirs import PlatformDirs
|
||||
from pathlib import Path
|
||||
from datetime import datetime, date
|
||||
from logging import Logger
|
||||
from typing import Any, Dict, List
|
||||
import os
|
||||
import json
|
||||
|
||||
from schema import Base, KontorDB
|
||||
from config import get_logger
|
||||
from pathlib import Path
|
||||
import requests
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from db.models.base import Base
|
||||
from db.models import registry
|
||||
from psycopg2.errors import NotNullViolation
|
||||
from config import get_api_config, get_logger
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--recreate-db', action='store_true')
|
||||
parser.add_argument("--config", "-c", default="kontor-docker")
|
||||
parser.add_argument('--dry-run', '-m', action='store_true')
|
||||
parser.add_argument('--cleanup', '-d', action='store_true')
|
||||
parser.add_argument('--file', '-f', default='~/data.json')
|
||||
args = parser.parse_args()
|
||||
|
||||
DB_USER: str = os.getenv("DB_USER", "kontor")
|
||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
||||
DB_SERVER: str = os.getenv("DB_SERVER", "127.0.0.1")
|
||||
DB_PORT: int = int(os.getenv("DB_PORT", 5432))
|
||||
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
||||
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
||||
|
||||
|
||||
def cleanup_database(db: Session, log, dry_run: bool):
|
||||
log.debug("cleanup_database")
|
||||
# get tables from registry
|
||||
for table in registry:
|
||||
log.info(f"{table}")
|
||||
model = registry[table]
|
||||
entries = db.query(model).all()
|
||||
for entry in entries:
|
||||
if not dry_run:
|
||||
db.delete(entry)
|
||||
db.commit()
|
||||
|
||||
def load_data(filename: str, log) -> Dict[str, List[Dict[str, Any]]]:
|
||||
log.debug("load_data")
|
||||
import_file = Path(filename)
|
||||
if not import_file.exists():
|
||||
log.info(f"File {filename} does not exist. Do nothing.")
|
||||
raise FileNotFoundError()
|
||||
log.info("read json file")
|
||||
with open(filename, 'r') as json_file:
|
||||
json_load = json.load(json_file)
|
||||
return json_load
|
||||
|
||||
def get_ids(items: List[Any]) -> List[str]:
|
||||
result: List[str] = []
|
||||
for item in items:
|
||||
result.append(item.id)
|
||||
return result
|
||||
|
||||
def update_item(db: Session, import_data: Dict[str, Any], item: Any, dry_run: bool, log):
|
||||
for (key, value) in import_data.items():
|
||||
existing_value = getattr(item, str(key))
|
||||
update: bool = has_changed(existing_value, value, log)
|
||||
#if key == 'published_on':
|
||||
# log.info(f"{type(value)}:{value} != {type(existing_value)}:{existing_value} : {update}")
|
||||
if update:
|
||||
log.info(f"update {key}({existing_value}) with {value}")
|
||||
if not dry_run:
|
||||
if value == 'None':
|
||||
setattr(item, str(key), None)
|
||||
else:
|
||||
setattr(item, str(key), value)
|
||||
db.add(item)
|
||||
db.commit()
|
||||
|
||||
def has_changed(existing_data: Any, import_data: str, log) -> bool:
|
||||
if existing_data is None and import_data == 'None':
|
||||
return False
|
||||
if isinstance(existing_data, str):
|
||||
return existing_data != import_data
|
||||
if isinstance(existing_data, date):
|
||||
if len(import_data) > 19:
|
||||
import_date = datetime.strptime(import_data, "%Y-%m-%d %H:%M:%S.%f")
|
||||
log.debug(f"{type(existing_data)}:{existing_data} == {import_date} : {existing_data != import_date}")
|
||||
return existing_data != import_date
|
||||
if len(import_data) > 10:
|
||||
import_date = datetime.strptime(import_data, "%Y-%m-%d %H:%M:%S")
|
||||
log.debug(f"{type(existing_data)}:{existing_data} == {import_date} : {existing_data != import_date}")
|
||||
return existing_data != import_date
|
||||
return existing_data.strftime("%Y-%m-%d") != import_data
|
||||
return existing_data != import_data
|
||||
|
||||
|
||||
def item_import(db: Session, import_data: Dict[str, Any], dry_run: bool, log):
|
||||
log.info(f"import {import_data}")
|
||||
if not dry_run:
|
||||
log.debug(f"model: {repr(model)} {import_data}")
|
||||
try:
|
||||
new_item = model()
|
||||
new_item.import_dict(import_data)
|
||||
log.info(f"new item: {new_item}")
|
||||
db.add(new_item)
|
||||
db.commit()
|
||||
except NotNullViolation as notnull:
|
||||
log.info(f"import failed: {notnull} {import_data}")
|
||||
except Exception as error:
|
||||
log.info(f"import failed: {error}")
|
||||
|
||||
def item_delete(table_name: str, item_id: str, api_data: Dict[str, Any], log: Logger):
|
||||
log.info(f"delete item {item_id} from {table_name}")
|
||||
host = api_data["host"]
|
||||
port = api_data["port"]
|
||||
token = api_data['token']
|
||||
url = ""
|
||||
match table_name:
|
||||
case "media_file":
|
||||
url = f"http://{host}:{port}/api/media/files/{item_id}"
|
||||
case "media_actor_file":
|
||||
url = f"http://{host}:{port}/api/media/actorfiles/{item_id}"
|
||||
case "media_actor":
|
||||
url = f"http://{host}:{port}/api/media/actors/{item_id}"
|
||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(url, headers=headers)
|
||||
log.debug(f"Status: {response.status_code}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose, args.config)
|
||||
logger = get_logger(args.verbose, "kontor")
|
||||
logger.info('kontor.import started')
|
||||
dirs = PlatformDirs(args.config)
|
||||
database_config = Path(dirs.user_config_dir, 'database-config.yaml')
|
||||
with open(database_config, 'rt') as f:
|
||||
db_config = yaml.safe_load(f.read())
|
||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
||||
db_config['mariadb']['user'],
|
||||
db_config['mariadb']['password'],
|
||||
db_config['mariadb']['host'],
|
||||
db_config['mariadb']['port'],
|
||||
db_config['mariadb']['database']
|
||||
))
|
||||
engine = create_engine(connect_string)
|
||||
api_data = get_api_config(logger, args.config)
|
||||
engine = create_engine(DATABASE_URL)
|
||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||
__session__ = sessionmaker(bind=engine)
|
||||
kontor_db = KontorDB(engine, logger)
|
||||
kontor_db.import_db(args.file)
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
with SessionLocal() as db:
|
||||
if args.cleanup:
|
||||
cleanup_database(db, logger, args.dry_run)
|
||||
data: Dict[str, List[Dict[str, Any]]] = load_data(args.file, logger)
|
||||
table_list: List[str] = list(data.keys())
|
||||
logger.debug(f"Liste der Tabellen: {table_list}")
|
||||
sorted_table_list: List[str] = table_list
|
||||
for tablename in sorted_table_list:
|
||||
model = registry[tablename]
|
||||
existing_items = db.query(model).all()
|
||||
existing_ids: List[str] = get_ids(existing_items)
|
||||
logger.debug(f"found {len(existing_items)} for table {tablename}")
|
||||
import_items: List[Dict[str, Any]] = data[tablename]
|
||||
for import_item in import_items:
|
||||
item_id: str = import_item['id']
|
||||
if item_id in existing_ids:
|
||||
logger.debug(f"update {item_id}")
|
||||
existing_item = db.get(model, item_id)
|
||||
update_item(db, import_item, existing_item, args.dry_run, logger)
|
||||
existing_ids.remove(item_id)
|
||||
else:
|
||||
logger.debug(f"import {item_id}")
|
||||
item_import(db, import_item, args.dry_run, logger)
|
||||
logger.debug(f"remaining items for {tablename}: {len(existing_ids)}")
|
||||
if len(existing_ids) > 0:
|
||||
logger.info(f"remaining items for {tablename}: {existing_ids}")
|
||||
for item_id in existing_ids:
|
||||
match tablename:
|
||||
case "media_file":
|
||||
item_delete(table_name=tablename, item_id=item_id, api_data=api_data, log=logger)
|
||||
case "media_actor_file":
|
||||
item_delete(table_name=tablename, item_id=item_id, api_data=api_data, log=logger)
|
||||
case "media_actor":
|
||||
item_delete(table_name=tablename, item_id=item_id, api_data=api_data, log=logger)
|
||||
case _:
|
||||
logger.info("Method to remove remaining item not implemented")
|
||||
logger.info('kontor.import finished')
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
copy data from JSON to Postgres
|
||||
"""
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from config import get_logger, get_database_cursors
|
||||
import json
|
||||
from psycopg2.sql import SQL
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
parser.add_argument('--file', '-f', default='~/.sync/media/data.json')
|
||||
args = parser.parse_args()
|
||||
|
||||
def copy_data(postgres_conn, data_file: Path, log):
|
||||
postgres_cursor = postgres_conn.cursor()
|
||||
import_file = Path(data_file)
|
||||
if not import_file.exists():
|
||||
log.info(f"File {data_file} does not exist. Do nothing.")
|
||||
return
|
||||
log.info("read json file")
|
||||
with open(data_file, 'r') as json_file:
|
||||
json_load = json.load(json_file)
|
||||
postgres_cursor.execute("SET session_replication_role='replica'")
|
||||
for table in json_load:
|
||||
log.info(f"{table}: {len(json_load[table])}")
|
||||
# result[table] = import_table(table, json_load[table])
|
||||
truncate_statement = 'TRUNCATE {} CASCADE'.format(table)
|
||||
#log.info(f"truncate: {truncate_statement}")
|
||||
try:
|
||||
postgres_cursor.execute(truncate_statement)
|
||||
except:
|
||||
log.info(f"statement: {insert_statement} FAILED")
|
||||
items = json_load[table]
|
||||
for item in items:
|
||||
#log.info(f"item: {item}")
|
||||
values = []
|
||||
columns = []
|
||||
for (key, value) in item.items():
|
||||
columns.append(key)
|
||||
values.append(value)
|
||||
row = tuple(values)
|
||||
#log.info(f"values: {row}")
|
||||
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns), ', '.join(['%s']*len(columns)))
|
||||
#log.info(f"statement: {insert_statement}")
|
||||
try:
|
||||
postgres_cursor.execute(SQL(insert_statement), row)
|
||||
postgres_conn.commit()
|
||||
except:
|
||||
log.info(f'insert failed with {insert_statement}')
|
||||
postgres_cursor.execute("SET session_replication_role='origin'")
|
||||
|
||||
|
||||
def load_json(data_file, log) -> dict:
|
||||
import_file = Path(data_file)
|
||||
if not import_file.exists():
|
||||
log.info(f"File {data_file} does not exist. Do nothing.")
|
||||
return
|
||||
log.info("read json file")
|
||||
with open(data_file, 'r') as json_file:
|
||||
json_load = json.load(json_file)
|
||||
return json_load
|
||||
|
||||
|
||||
def insert_data(postgres_conn, data: dict, log):
|
||||
postgres_cursor = postgres_conn.cursor()
|
||||
log.info("insert data")
|
||||
table_list = []
|
||||
table_list = ['worktype', 'artist', 'publisher', 'volume', 'comic', 'issue', 'story_arc', 'trade_paperback', 'comic_work']
|
||||
table_list.extend(['sport', 'team', 'field_position', 'vendor', 'player', 'rooster', 'card_set', 'card'])
|
||||
#table_list.extend(['card'])
|
||||
table_list.extend(['media_file', 'media_video', 'media_actor', 'media_article', 'media_actor_file'])
|
||||
#table_list.extend(['media_actor_file'])
|
||||
table_list.extend(['profile', 'permission', 'token', 'assignment'])
|
||||
table_list.extend(['mail', 'mail_account', 'module_data', 'meta_data_table', 'meta_data_column'])
|
||||
table_list.extend(['meta_data_table', 'meta_data_column'])
|
||||
table_list.extend(['book', 'author', 'article', 'bookshelf_publisher', 'book_author', 'article_author'])
|
||||
#if len(table_list) != 37:
|
||||
# log.info(f"number of tables incorrect: {len(table_list)}")
|
||||
# return
|
||||
for table in table_list:
|
||||
log.info(f"{table}: {len(data[table])}")
|
||||
truncate_statement = 'DELETE FROM {}'.format(table)
|
||||
log.info(f"truncate: {truncate_statement}")
|
||||
try:
|
||||
postgres_cursor.execute(truncate_statement)
|
||||
postgres_conn.commit()
|
||||
except:
|
||||
log.info(f"statement: {truncate_statement} FAILED")
|
||||
items = data[table]
|
||||
for item in items:
|
||||
# log.info(f"item: {item}")
|
||||
values = []
|
||||
columns = []
|
||||
for (key, value) in item.items():
|
||||
columns.append(key)
|
||||
values.append(value)
|
||||
row = tuple(values)
|
||||
# log.info(f"values: {row}")
|
||||
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns),
|
||||
', '.join(['%s'] * len(columns)))
|
||||
# log.info(f"statement: {insert_statement}")
|
||||
try:
|
||||
postgres_cursor.execute(SQL(insert_statement), row)
|
||||
postgres_conn.commit()
|
||||
except:
|
||||
log.info(f'insert failed with {insert_statement}')
|
||||
|
||||
|
||||
def parse_table_order(data: dict, log):
|
||||
log.info("parse_table_order")
|
||||
table_refs: Dict[str, List[str]] = {}
|
||||
for table in data:
|
||||
log.info(f"{table}: {len(data[table])}")
|
||||
items = data[table]
|
||||
table_refs[table] = []
|
||||
if len(items) == 0:
|
||||
continue
|
||||
item = items[0]
|
||||
for key, _ in item.items():
|
||||
if key.endswith('_id'):
|
||||
ref = key[0:-3]
|
||||
log.info(f"table {table} has reference to {ref}")
|
||||
if table in table_refs:
|
||||
table_refs[table].append(ref)
|
||||
else:
|
||||
table_refs[table] = [ref]
|
||||
log.info(f"parsed refs: {table_refs}")
|
||||
table_order = []
|
||||
for table in table_refs:
|
||||
if len(table_refs[table]) == 0:
|
||||
log.info(f"insert {table} at beginning")
|
||||
table_order.insert(0, table)
|
||||
else:
|
||||
log.info(f"insert {table} at end")
|
||||
table_order.append(table)
|
||||
log.info(f"table_list: {len(table_order)}: {table_order}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose, args.config)
|
||||
logger.info('kontor.json_to_postgres started')
|
||||
_, _, p_conn = get_database_cursors(logger, args.config)
|
||||
data = load_json(args.file, logger)
|
||||
#parse_table_order(data, logger)
|
||||
insert_data(p_conn, data, logger)
|
||||
#copy_data(p_conn, args.file, logger)
|
||||
p_conn.close()
|
||||
logger.info('kontor.json_to_postgres finished')
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from pathlib import Path
|
||||
|
||||
import logging.config
|
||||
import yaml
|
||||
from platformdirs import PlatformDirs
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from schema import Comic, Publisher, Base
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--recreate-db', action='store_true')
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--file', '-f', default='~/data.json')
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def get_logger(level: int, config: str):
|
||||
dirs = PlatformDirs(config)
|
||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
||||
with open(logging_config, 'rt') as f:
|
||||
configDict = yaml.safe_load(f.read())
|
||||
logging.config.dictConfig(configDict)
|
||||
logger = logging.getLogger('development')
|
||||
if level is not None:
|
||||
match level:
|
||||
case 0:
|
||||
logger.setLevel(logging.INFO)
|
||||
case 1:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
case _:
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
return logger
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info('kontor started')
|
||||
dirs = PlatformDirs(args.config)
|
||||
database_config = Path(dirs.user_config_dir, 'database-config.yaml')
|
||||
with open(database_config, 'rt') as f:
|
||||
db_config = yaml.safe_load(f.read())
|
||||
print(db_config)
|
||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
||||
db_config['mariadb']['user'],
|
||||
db_config['mariadb']['password'],
|
||||
db_config['mariadb']['host'],
|
||||
db_config['mariadb']['port'],
|
||||
db_config['mariadb']['database']
|
||||
))
|
||||
engine = create_engine(connect_string)
|
||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||
__session__ = sessionmaker(engine)
|
||||
with __session__() as session:
|
||||
comics = session.scalars(select(Comic)).all()
|
||||
for comic in comics:
|
||||
print(comic)
|
||||
publishers = session.scalars(select(Publisher)).all()
|
||||
for publisher in publishers:
|
||||
print(publisher)
|
||||
print(len(publisher.comics))
|
||||
log.info('kontor finished')
|
||||
@@ -1,4 +1,5 @@
|
||||
[project]
|
||||
requires-python = ">=3.13"
|
||||
name = "kontor-scripts"
|
||||
version = "0.1.0"
|
||||
readme = "README.md"
|
||||
@@ -10,12 +11,17 @@ maintainers = [
|
||||
]
|
||||
dependencies = [
|
||||
"beautifulsoup4>=4.13.4",
|
||||
"coverage>=7.8.0",
|
||||
"fastapi[standard]>=0.115.12",
|
||||
"mariadb>=1.1.12",
|
||||
"pathlib>=1.0.1",
|
||||
"platformdirs>=4.3.7",
|
||||
"proton>=0.9.1",
|
||||
"psycopg2-binary>=2.9.10",
|
||||
"pytest-cov>=6.1.1",
|
||||
"python-qpid-proton>=0.40.0",
|
||||
"pyyaml>=6.0.2",
|
||||
"requests>=2.32.3",
|
||||
"sqlalchemy>=2.0.40",
|
||||
"sqlmodel>=0.0.24",
|
||||
"stomp.py",
|
||||
]
|
||||
|
||||
+56
-33
@@ -1,12 +1,12 @@
|
||||
"""
|
||||
read file with URLs and store in DB
|
||||
"""
|
||||
import uuid
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
import datetime
|
||||
|
||||
import mariadb
|
||||
from setup import get_database_cursors, get_logger, get_scripts, get_meta_data
|
||||
import logging
|
||||
import json
|
||||
from proton import Message, Event
|
||||
from proton.handlers import MessagingHandler
|
||||
from proton.reactor import Container
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('-f', '--links', help='file with links')
|
||||
@@ -20,38 +20,61 @@ def read_links_file(links_file):
|
||||
return lines
|
||||
|
||||
|
||||
def add_link_to_db(statement, connection, video_url, log):
|
||||
entry_id = str(uuid.uuid4())
|
||||
current_date_time = datetime.datetime.now()
|
||||
try:
|
||||
cur = connection.cursor()
|
||||
cur.execute(statement, (entry_id, current_date_time, current_date_time, 0, video_url, True, True, None, None, None, None))
|
||||
connection.commit()
|
||||
log.info(f'link {video_url} added to db')
|
||||
except mariadb.Error as insert_error:
|
||||
log.debug("insert failed with %s", insert_error)
|
||||
entry_id = None
|
||||
return entry_id
|
||||
class AddLinkMessage(MessagingHandler):
|
||||
def __init__(self, server, url, log):
|
||||
super(AddLinkMessage, self).__init__()
|
||||
log.info("create AddLinkMessage")
|
||||
self.server = server
|
||||
self.address = "KontorMediaFile::add_link_file"
|
||||
self.url = url
|
||||
self.log = log
|
||||
|
||||
def on_start(self, event: Event):
|
||||
self.log.info("Connection...")
|
||||
conn = event.container.connect(self.server, user="artemis", password="artemis")
|
||||
event.container.create_sender(conn, self.address)
|
||||
|
||||
def on_connection_opened(self, event: Event) -> None:
|
||||
self.log.debug("connection open")
|
||||
|
||||
def on_connection_error(self, event: Event) -> None:
|
||||
self.log.info(f"error: {event}")
|
||||
|
||||
def on_disconnected(self, event: Event) -> None:
|
||||
self.log.debug(f"disconnected: {repr(event)}")
|
||||
|
||||
|
||||
def on_sendable(self, event: Event):
|
||||
self.log.info("send message")
|
||||
message = Message(body=self.url, address=self.address, content_type="application/json", durable=True)
|
||||
delivery = event.sender.send(message)
|
||||
self.log.info(f"Delivery {delivery} sent")
|
||||
event.connection.close()
|
||||
|
||||
def on_accepted(self, event: Event) -> None:
|
||||
self.log.info(f"accepted Delivery: {event.delivery.remote_state}")
|
||||
|
||||
|
||||
def on_rejected(self, event: Event) -> None:
|
||||
self.log.info(f"rejected Delivery: {event.delivery}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = get_logger(args.verbose)
|
||||
logger.info('kontor.read_list started')
|
||||
s_conn, m_conn = get_database_cursors(logger)
|
||||
meta_data_tables = get_meta_data(m_conn)
|
||||
scripts = get_scripts(meta_data_tables, logger)
|
||||
tables = {}
|
||||
for table_id in scripts:
|
||||
tables[scripts[table_id]['name']] = table_id
|
||||
media_file_id = tables['media_file']
|
||||
insert_statement = scripts[tables['media_file']]['insert_mariadb']
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s')
|
||||
logging.info('kontor.read_list started')
|
||||
#conn = stomp.Connection([('127.0.0.1', '61616')])
|
||||
#conn.connect('artemis', 'artemis', wait=True)
|
||||
if args.links:
|
||||
logger.info("read links from file")
|
||||
logging.info("read links from file")
|
||||
links = read_links_file(args.links)
|
||||
for link in links:
|
||||
logger.info("add link to db")
|
||||
add_link_to_db(insert_statement, m_conn, link.strip(), logger)
|
||||
else:
|
||||
logger.info('script used: {}'.format(insert_statement))
|
||||
logger.info('kontor.read_list finished')
|
||||
data_dict = {'url': link.strip()}
|
||||
data = json.dumps(data_dict)
|
||||
logging.info("send link message")
|
||||
handler = AddLinkMessage("amqp://127.0.0.1:5672", data, logging)
|
||||
container = Container(handler)
|
||||
container.container_id = "process_add_links"
|
||||
container.run()
|
||||
# conn.send(body=data, destination='KontorMediaFile::add_link_file', headers={'content-type': 'application/json'})
|
||||
#conn.disconnect()
|
||||
logging.info('kontor.read_list finished')
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import stomp
|
||||
import json
|
||||
import time
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
from config import get_logger
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
||||
args = parser.parse_args()
|
||||
|
||||
class MyListener(stomp.ConnectionListener):
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
|
||||
def on_error(self, frame):
|
||||
self.log.info(f"received an error {frame.body}")
|
||||
|
||||
def on_message(self, frame):
|
||||
self.log.info(f"received a message '{frame.body}'")
|
||||
data = json.loads(frame.body)
|
||||
url = data['url']
|
||||
self.log.info(f"found link: {url}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info("kontor.read_queue started")
|
||||
host = [('127.0.0.1', 61616)]
|
||||
conn = stomp.Connection(host_and_ports=host)
|
||||
conn.set_listener('', MyListener(log))
|
||||
conn.connect(username='artemis', passcode='artemis', wait=True)
|
||||
conn.subscribe(destination='KontorMediaFile::add_link_file', id=1, ack='auto', headers={})
|
||||
time.sleep(5)
|
||||
conn.disconnect()
|
||||
log.info("kontor.read_queue finished")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mariadb
|
||||
sqlalchemy
|
||||
pathlib
|
||||
platformdirs
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
|
||||
|
||||
class Profile(Base, BaseMixin):
|
||||
__tablename__ = 'profile'
|
||||
first_name = Column(String(255))
|
||||
last_name = Column(String(255))
|
||||
user_name = Column(String(255), nullable=False)
|
||||
email = Column(String(255))
|
||||
password = Column(String(255))
|
||||
enabled = Column(BIT(1))
|
||||
assignments = relationship("Assignment")
|
||||
tokens = relationship("Token")
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
full_name = ""
|
||||
if self.first_name is not None:
|
||||
full_name += self.first_name
|
||||
if self.last_name is not None:
|
||||
if len(full_name) > 0:
|
||||
full_name += " "
|
||||
full_name += self.last_name
|
||||
return full_name
|
||||
|
||||
|
||||
class Token(Base, BaseMixin):
|
||||
__tablename__ = "token"
|
||||
token = Column(String(255), nullable=False, unique=True)
|
||||
name = Column(String(255))
|
||||
last_used_date: Mapped[datetime] = mapped_column()
|
||||
enabled = Column(BIT(1))
|
||||
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
|
||||
profile = relationship("Profile", back_populates="tokens")
|
||||
|
||||
|
||||
class Permission(Base, BaseMixin):
|
||||
__tablename__ = "permission"
|
||||
name = Column(String(255), nullable=False)
|
||||
assignments = relationship("Assignment")
|
||||
|
||||
|
||||
class Assignment(Base, BaseMixin):
|
||||
__tablename__ = "assignment"
|
||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
||||
profile = relationship("Profile", back_populates="assignments")
|
||||
permission_id = Column(String, ForeignKey("permission.id"), nullable=False)
|
||||
permission = relationship("Permission", back_populates="assignments")
|
||||
|
||||
|
||||
class ModuleData(Base, BaseMixin):
|
||||
__tablename__ = "module_data"
|
||||
module_name = Column(String(255), nullable=False)
|
||||
import_data = Column(BIT(1))
|
||||
|
||||
|
||||
class MailAccount(Base, BaseMixin):
|
||||
__tablename__ = "mail_account"
|
||||
host = Column(String(255))
|
||||
port = Column(Integer)
|
||||
protocol = Column(String(255))
|
||||
user_name = Column(String(255))
|
||||
password = Column(String(255))
|
||||
start_tls = Column(BIT(1))
|
||||
|
||||
|
||||
class Mail(Base, BaseMixin):
|
||||
__tablename__ = "mail"
|
||||
folder: Mapped[str] = mapped_column()
|
||||
subject: Mapped[str] = mapped_column()
|
||||
body: Mapped[str] = mapped_column()
|
||||
sent_date: Mapped[datetime] = mapped_column()
|
||||
received_date: Mapped[datetime] = mapped_column()
|
||||
@@ -1,50 +0,0 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
|
||||
|
||||
class Article(Base, BaseMixin):
|
||||
__tablename__ = 'article'
|
||||
title = Column(String(length=255), unique=True)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
|
||||
|
||||
class Author(Base, BaseMixin):
|
||||
__tablename__ = 'author'
|
||||
first_name = Column(String(255))
|
||||
last_name = Column(String(255))
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
|
||||
class BookshelfPublisher(Base, BaseMixin):
|
||||
__tablename__ = 'bookshelf_publisher'
|
||||
name = Column(String(length=255), unique=True)
|
||||
books = relationship("Book")
|
||||
|
||||
|
||||
class Book(Base, BaseMixin):
|
||||
__tablename__ = 'book'
|
||||
isbn = Column(String(255), unique=True)
|
||||
title = Column(String(255))
|
||||
year = Column(Integer, nullable=False)
|
||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
|
||||
class ArticleAuthor(Base, BaseMixin):
|
||||
__tablename__ = 'article_author'
|
||||
article_id = Column(String, ForeignKey('article.id'), nullable=False)
|
||||
article = relationship('Article', back_populates="article_authors")
|
||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
||||
author = relationship('Author', back_populates="article_authors")
|
||||
|
||||
|
||||
class BookAuthor(Base, BaseMixin):
|
||||
__tablename__ = 'book_author'
|
||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
||||
author = relationship('Author', back_populates="book_authors")
|
||||
book_id = Column(String, ForeignKey('book.id'), nullable=False)
|
||||
book = relationship('Book', back_populates="book_authors")
|
||||
@@ -1,100 +0,0 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
|
||||
|
||||
class Publisher(Base, BaseMixin):
|
||||
__tablename__ = "publisher"
|
||||
name = Column(String(length=255), unique=True)
|
||||
comics = relationship("Comic")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Publisher({self.id} {self.name})'
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class Comic(Base, BaseMixin):
|
||||
__tablename__ = 'comic'
|
||||
title = Column(String(length=255), unique=True)
|
||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(BIT(1))
|
||||
completed = Column(BIT(1))
|
||||
issues = relationship("Issue")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
volumes = relationship("Volume")
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title}({self.id})'
|
||||
|
||||
|
||||
class Volume(Base, BaseMixin):
|
||||
__tablename__ = "volume"
|
||||
name = Column(String(length=255), nullable=False)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="volumes")
|
||||
issues = relationship("Issue")
|
||||
|
||||
|
||||
class TradePaperback(Base, BaseMixin):
|
||||
__tablename__ = "trade_paperback"
|
||||
name = Column(String(length=255), nullable=False)
|
||||
issue_start = Column(Integer)
|
||||
issue_end = Column(Integer)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="trade_paperbacks")
|
||||
|
||||
|
||||
class StoryArc(Base, BaseMixin):
|
||||
__tablename__ = "story_arc"
|
||||
name = Column(String(length=255), nullable=False)
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="story_arcs")
|
||||
|
||||
|
||||
class Issue(Base, BaseMixin):
|
||||
__tablename__ = "issue"
|
||||
issue_number = Column(String(255))
|
||||
in_stock = Column(BIT(1))
|
||||
is_read = Column(BIT(1))
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="issues")
|
||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||
volume = relationship("Volume", back_populates="issues")
|
||||
|
||||
|
||||
class Artist(Base, BaseMixin):
|
||||
__tablename__ = "artist"
|
||||
name = Column(String(length=255), nullable=False)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
|
||||
class WorkType(Base, BaseMixin):
|
||||
__tablename__ = "worktype"
|
||||
name = Column(String(length=255), nullable=False, unique=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def __repr__(self):
|
||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name}({self.id})'
|
||||
|
||||
|
||||
class ComicWork(Base, BaseMixin):
|
||||
__tablename__ = "comic_work"
|
||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||
comic = relationship("Comic", back_populates="comic_works")
|
||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
||||
artist = relationship("Artist", back_populates="comic_works")
|
||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||
work_type = relationship("WorkType", back_populates="comic_works")
|
||||
@@ -1,399 +0,0 @@
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum, auto
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import UUID, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from .tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||
from .comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||
from .bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||
from .admin import Mail, MailAccount, ModuleData, Permission, Profile, Token, Assignment
|
||||
from .metadata import MetaDataTable, MetaDataColumn
|
||||
from .media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||
|
||||
|
||||
class ColumnEntry(Enum):
|
||||
COLUMN_NAME = 'column'
|
||||
COLUMN_LABEL = 'label'
|
||||
COLUMN_ORDER = 'order'
|
||||
COLUMN_REF_COLUMN = 'ref_column'
|
||||
COLUMN_TYPE = 'type'
|
||||
COLUMN_WIDGET = 'widget'
|
||||
|
||||
|
||||
class StatusType(Enum):
|
||||
UNKNOWN = auto()
|
||||
FILE_NAME = auto()
|
||||
FILE_ID = auto()
|
||||
DUPLICATE = auto()
|
||||
CLOUD_LINK = auto()
|
||||
CLOUD_LINK_ID = auto()
|
||||
|
||||
|
||||
class ExportType(Enum):
|
||||
JSON = "JSON"
|
||||
YAML = "YAML"
|
||||
SQLITE = "SQLite"
|
||||
|
||||
|
||||
class KontorDB:
|
||||
|
||||
def __init__(self, db_engine: Any, log: Logger):
|
||||
self.engine = db_engine
|
||||
self.registry = {}
|
||||
self.init_registry()
|
||||
self.log = log
|
||||
|
||||
def init_registry(self):
|
||||
self.registry[Card.__tablename__] = Card
|
||||
self.registry[CardSet.__tablename__] = CardSet
|
||||
self.registry[Rooster.__tablename__] = Rooster
|
||||
self.registry[Team.__tablename__] = Team
|
||||
self.registry[FieldPosition.__tablename__] = FieldPosition
|
||||
self.registry[Player.__tablename__] = Player
|
||||
self.registry[Vendor.__tablename__] = Vendor
|
||||
self.registry[Sport.__tablename__] = Sport
|
||||
self.registry[Issue.__tablename__] = Issue
|
||||
self.registry[TradePaperback.__tablename__] = TradePaperback
|
||||
self.registry[StoryArc.__tablename__] = StoryArc
|
||||
self.registry[Volume.__tablename__] = Volume
|
||||
self.registry[ComicWork.__tablename__] = ComicWork
|
||||
self.registry[Artist.__tablename__] = Artist
|
||||
self.registry[Comic.__tablename__] = Comic
|
||||
self.registry[Publisher.__tablename__] = Publisher
|
||||
self.registry[WorkType.__tablename__] = WorkType
|
||||
self.registry[ArticleAuthor.__tablename__] = ArticleAuthor
|
||||
self.registry[BookAuthor.__tablename__] = BookAuthor
|
||||
self.registry[BookshelfPublisher.__tablename__] = BookshelfPublisher
|
||||
self.registry[Article.__tablename__] = Article
|
||||
self.registry[Book.__tablename__] = Book
|
||||
self.registry[Author.__tablename__] = Author
|
||||
self.registry[MediaFile.__tablename__] = MediaFile
|
||||
self.registry[MediaActor.__tablename__] = MediaActor
|
||||
self.registry[MediaActorFile.__tablename__] = MediaActorFile
|
||||
self.registry[MediaArticle.__tablename__] = MediaArticle
|
||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
||||
self.registry[Assignment.__tablename__] = Assignment
|
||||
self.registry[Token.__tablename__] = Token
|
||||
self.registry[Profile.__tablename__] = Profile
|
||||
self.registry[Permission.__tablename__] = Permission
|
||||
self.registry[ModuleData.__tablename__] = ModuleData
|
||||
self.registry[MailAccount.__tablename__] = MailAccount
|
||||
self.registry[Mail.__tablename__] = Mail
|
||||
|
||||
def get_table_names(self) -> list:
|
||||
result = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
tables = session.scalars(select(MetaDataTable)).all()
|
||||
result = [table.table_name for table in tables]
|
||||
return result
|
||||
|
||||
def get_table_by_name(self, table_name: str) -> dict:
|
||||
result = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
_filter = {'table_name': table_name}
|
||||
with __session__() as session:
|
||||
table = session.query(MetaDataTable).filter_by(**_filter).one()
|
||||
result['id'] = table.id
|
||||
result['table_name'] = table.table_name
|
||||
return result
|
||||
|
||||
def get_column_meta_data(self, table_name: str, view_only=True) -> dict:
|
||||
meta_data = {}
|
||||
order = 0
|
||||
__session__ = sessionmaker(self.engine)
|
||||
columns = list()
|
||||
table_info = self.get_table_by_name(table_name)
|
||||
_filters = {'table_id': table_info['id']}
|
||||
if view_only:
|
||||
_filters['is_shown'] = True
|
||||
with __session__() as session:
|
||||
columns = session.query(MetaDataColumn).filter_by(**_filters).all()
|
||||
for column in columns:
|
||||
# self.log.info("get_column_meta_data: %s %s %d", column.column_name, column.column_label, column.column_order)
|
||||
meta_data[order] = {
|
||||
ColumnEntry.COLUMN_NAME: column.column_name,
|
||||
ColumnEntry.COLUMN_LABEL: column.column_label,
|
||||
ColumnEntry.COLUMN_ORDER: column.column_order,
|
||||
ColumnEntry.COLUMN_REF_COLUMN: column.ref_column,
|
||||
ColumnEntry.COLUMN_TYPE: column.column_type
|
||||
}
|
||||
order += 1
|
||||
return meta_data
|
||||
|
||||
def get_columns(self, table_name: str) -> dict:
|
||||
columns = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table_info = self.get_table_by_name(table_name)
|
||||
_filters = {'table_id': table_info['id']}
|
||||
with __session__() as session:
|
||||
for column in session.query(MetaDataColumn).filter_by(**_filters).all():
|
||||
columns[column.column_name] = {
|
||||
ColumnEntry.COLUMN_ORDER: column.column_order,
|
||||
ColumnEntry.COLUMN_TYPE: column.column_type
|
||||
}
|
||||
return columns
|
||||
|
||||
def get_filters(self, table_name: str) -> dict:
|
||||
_filter_map = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table_info = self.get_table_by_name(table_name)
|
||||
_filters = {'table_id': table_info['id'], 'show_filter': True}
|
||||
with __session__() as session:
|
||||
for column in session.query(MetaDataColumn).filter_by(**_filters).all():
|
||||
_filter_map[column.column_name] = {
|
||||
ColumnEntry.COLUMN_LABEL: column.filter_label,
|
||||
ColumnEntry.COLUMN_WIDGET: None
|
||||
}
|
||||
return _filter_map
|
||||
|
||||
def data(self, table_name: str, columns: dict, filters: dict) -> list:
|
||||
data = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table = self.registry[table_name]
|
||||
with __session__() as session:
|
||||
entries = []
|
||||
if len(filters) == 0:
|
||||
entries = session.scalars(select(table)).all()
|
||||
else:
|
||||
entries = session.scalars(select(table).filter_by(**filters)).all()
|
||||
for entry in entries:
|
||||
# self.log.info("data: %s", entry)
|
||||
row = []
|
||||
for order in columns.keys():
|
||||
column_name = columns[order][ColumnEntry.COLUMN_NAME]
|
||||
ref_column = columns[order][ColumnEntry.COLUMN_REF_COLUMN]
|
||||
if str(column_name).endswith("_id"):
|
||||
ref_table = column_name[:-3]
|
||||
ref = getattr(entry, ref_table)
|
||||
value = getattr(ref, ref_column)
|
||||
row.append(value)
|
||||
else:
|
||||
row.append(getattr(entry, column_name))
|
||||
data.append(row)
|
||||
# self.log.info("data: %s", data)
|
||||
return data
|
||||
|
||||
def export_db(self, export_type: ExportType, export_file_name: str) -> dict:
|
||||
results = {}
|
||||
db = {}
|
||||
export_table_list = self.get_table_names()
|
||||
for table in export_table_list:
|
||||
columns = self.get_column_meta_data(table, view_only=False)
|
||||
if table in self.registry:
|
||||
model = self.registry[table]
|
||||
else:
|
||||
self.log.info(f"table {table} is not registered")
|
||||
continue
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
rows = session.query(model).all()
|
||||
entries = []
|
||||
for row in rows:
|
||||
# print(row)
|
||||
entry = {}
|
||||
for order in columns:
|
||||
# print(columns[order])
|
||||
column_name = columns[order][ColumnEntry.COLUMN_NAME]
|
||||
# print(f"get value {column_name} from {row} of table {table}")
|
||||
try:
|
||||
value = getattr(row, column_name)
|
||||
if isinstance(value, datetime):
|
||||
entry[column_name] = str(value)
|
||||
else:
|
||||
entry[column_name] = value
|
||||
except AttributeError:
|
||||
pass
|
||||
entries.append(entry)
|
||||
db[table] = entries
|
||||
results[table] = len(entries)
|
||||
match export_type:
|
||||
case ExportType.JSON:
|
||||
json_dump = json.dumps(db, indent=4)
|
||||
with open(export_file_name, "w") as dump_file:
|
||||
dump_file.write(json_dump)
|
||||
case ExportType.YAML:
|
||||
pass
|
||||
case ExportType.SQLITE:
|
||||
pass
|
||||
self.log.info(f"{len(results)} tables exported")
|
||||
return results
|
||||
|
||||
def import_db(self, import_file_name: str) -> dict:
|
||||
result = {}
|
||||
import_file = Path(import_file_name)
|
||||
if not import_file.exists():
|
||||
self.log.info(f"File {import_file_name} does not exist. Do nothing.")
|
||||
return result
|
||||
match import_file.suffix:
|
||||
case '.json':
|
||||
print("read json file")
|
||||
with open(import_file_name, 'r') as json_file:
|
||||
json_load = json.load(json_file)
|
||||
for table in json_load:
|
||||
self.log.info(f"{table}: {len(json_load[table])}")
|
||||
result[table] = self.import_table(table, json_load[table])
|
||||
case '.yml':
|
||||
print("read yaml file")
|
||||
case '.yaml':
|
||||
print("read yaml file")
|
||||
case '.db':
|
||||
print("read sqlite file")
|
||||
return result
|
||||
|
||||
def import_table(self, table_name: str, items: list) -> dict:
|
||||
result = {}
|
||||
updated = []
|
||||
added = []
|
||||
remaining = []
|
||||
existing_ids = self.get_ids(table_name)
|
||||
self.log.info(f"found {len(existing_ids)} existing ids for table {table_name}")
|
||||
for item in items:
|
||||
current_id = item['id']
|
||||
# print(f"import item: {item}")
|
||||
found_item = None
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
found_item = session.get(self.registry[table_name], current_id)
|
||||
# print(f"found item: {found_item}")
|
||||
if found_item is not None:
|
||||
changed = self.update_entry(table_name, current_id, item)
|
||||
updated.append(item)
|
||||
if changed:
|
||||
self.log.info(f"{current_id} has changed")
|
||||
updated.append(item)
|
||||
existing_ids.remove(current_id)
|
||||
else:
|
||||
try:
|
||||
self.add_entry(table_name, item)
|
||||
added.append(item)
|
||||
except IntegrityError as error:
|
||||
self.log.info(f"Could not add item, due to: {error.detail}")
|
||||
if len(existing_ids) > 0:
|
||||
print(f"remaining items for {table_name}: {existing_ids}")
|
||||
remaining.extend(existing_ids)
|
||||
result['updated'] = updated
|
||||
result['added'] = added
|
||||
result['remaining'] = remaining
|
||||
return result
|
||||
|
||||
def get_ids(self, table_name: str) -> list:
|
||||
existing_ids = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
items = session.query(self.registry[table_name]).all()
|
||||
for item in items:
|
||||
existing_ids.append(getattr(item, 'id'))
|
||||
return existing_ids
|
||||
|
||||
def add_entry(self, table_name: str, update_item: dict):
|
||||
self.log.debug(f"add entry to table {table_name} with {update_item}")
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
add_item = self.registry[table_name]()
|
||||
for key in update_item.keys():
|
||||
update_value = update_item[key]
|
||||
setattr(add_item, key, update_value)
|
||||
session.add(add_item)
|
||||
session.commit()
|
||||
|
||||
def update_entry(self, table_name, current_id, update_item: dict) -> bool:
|
||||
# self.log.info("update entry to table %s", table_name)
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
existing_item = session.query(self.registry[table_name]).get(current_id)
|
||||
changed = False
|
||||
for key in update_item.keys():
|
||||
update_value = update_item[key]
|
||||
existing_value = getattr(existing_item, key)
|
||||
if type(existing_value) is not type(update_value):
|
||||
existing_value = str(existing_value)
|
||||
if existing_value != update_value:
|
||||
self.log.info(f"{key} has changed: {existing_value} != {update_value}")
|
||||
setattr(existing_item, key, update_value)
|
||||
session.commit()
|
||||
changed = True
|
||||
self.log.info(f"update {key} with {update_value}")
|
||||
return changed
|
||||
|
||||
def add_link(self, link: str) -> dict:
|
||||
result = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
media_file = MediaFile()
|
||||
media_file.id = str(uuid.uuid4())
|
||||
media_file.created_date = datetime.now()
|
||||
media_file.last_modified_date = datetime.now()
|
||||
media_file.version = 0
|
||||
media_file.url = link
|
||||
media_file.review = 1
|
||||
media_file.should_download = 1
|
||||
try:
|
||||
session.add(media_file)
|
||||
session.commit()
|
||||
result['added'] = {'url': media_file.url, 'title': media_file.title, 'review': media_file.review,
|
||||
'download': media_file.should_download}
|
||||
except IntegrityError as error:
|
||||
session.rollback()
|
||||
result['error'] = error.orig
|
||||
return result
|
||||
|
||||
def update_titles(self) -> dict:
|
||||
update_list = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
_filter = {'review': True}
|
||||
with __session__() as session:
|
||||
links = session.query(MediaFile).filter_by(**_filter).all()
|
||||
self.log.info("%d entries found for updating titles", len(links))
|
||||
for link in links:
|
||||
url = link.url
|
||||
if url is None:
|
||||
continue
|
||||
link.update_title()
|
||||
session.commit()
|
||||
update_list[link.id] = link.title
|
||||
return update_list
|
||||
|
||||
def get_download_list(self) -> list[UUID]:
|
||||
download_list = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
_filter = {'should_download': True}
|
||||
with __session__() as session:
|
||||
links = session.query(MediaFile).filter_by(**_filter).all()
|
||||
for link in links:
|
||||
url = link.url
|
||||
if url is None:
|
||||
continue
|
||||
download_list.append(link.id)
|
||||
return download_list
|
||||
|
||||
def download_file(self, entry_id: str, download_dir="/data/media", dl_tool="yt-dlp") -> str:
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
link = session.query(MediaFile).get(entry_id)
|
||||
link.download_file(download_dir, dl_tool)
|
||||
session.commit()
|
||||
file_name = link.file_name
|
||||
return file_name
|
||||
|
||||
def delete_entries(self):
|
||||
for (table_name, table) in self.registry.items():
|
||||
# self.log.info("delete entries from table %s", table_name)
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
items = session.query(table).all()
|
||||
for item in items:
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
|
||||
def check_files(self):
|
||||
pass
|
||||
@@ -1,99 +0,0 @@
|
||||
import re
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from sqlalchemy import Boolean, Column, False_, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin, BaseVideoMixin
|
||||
|
||||
|
||||
class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
||||
__tablename__ = 'media_file'
|
||||
media_actor_files = relationship("MediaActorFile")
|
||||
|
||||
def __repr__(self):
|
||||
return f'MediaFile({self.id} {self.title} {self.title})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.title}({self.id})'
|
||||
|
||||
def update_title(self) -> None:
|
||||
print(f"update title for {self.url}")
|
||||
try:
|
||||
r = requests.get(self.url)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
title = soup.title.string
|
||||
self.title = title
|
||||
self.review = False_
|
||||
except:
|
||||
self.title = None
|
||||
self.review = True
|
||||
self.last_modified_date = datetime.now()
|
||||
|
||||
def download_file(self, download_dir: str, dl_tool: str):
|
||||
print(f"download file for {self.url} to {download_dir}")
|
||||
result = subprocess.run([dl_tool, self.url], cwd=download_dir, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
output = result.stdout
|
||||
output = re.sub(' +', ' ', output)
|
||||
lines_list = output.splitlines()
|
||||
file_name = self.__parse_output__(lines_list)
|
||||
if file_name is None:
|
||||
self.review = True
|
||||
self.should_download = True
|
||||
self.file_name = None
|
||||
else:
|
||||
download_file = Path(file_name)
|
||||
self.should_download = False_
|
||||
self.file_name = download_file.name
|
||||
self.cloud_link = str(download_file.absolute())
|
||||
self.last_modified_date = datetime.now()
|
||||
|
||||
def __parse_output__(self, lines_list):
|
||||
self.file_name = None
|
||||
for line in lines_list:
|
||||
if 'has already been downloaded' in line:
|
||||
end_len = len(' has already been downloaded')
|
||||
self.file_name = line[11:-end_len]
|
||||
if 'Destination' in line:
|
||||
line_len = len(line)
|
||||
start_len = len('[download] Destination: ')
|
||||
file_len = line_len - start_len
|
||||
self.file_name = line[-file_len:]
|
||||
return self.file_name
|
||||
|
||||
|
||||
class MediaActor(Base, BaseMixin):
|
||||
__tablename__ = 'media_actor'
|
||||
name = Column(String(255))
|
||||
media_actor_files = relationship("MediaActorFile")
|
||||
|
||||
|
||||
class MediaActorFile(Base, BaseMixin):
|
||||
__tablename__ = 'media_actor_file'
|
||||
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
|
||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
||||
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
|
||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||
|
||||
|
||||
class MediaArticle(Base, BaseMixin):
|
||||
__tablename__ = 'media_article'
|
||||
review = Column(Boolean)
|
||||
title = Column(String(255))
|
||||
url = Column(String(255), unique=True)
|
||||
|
||||
|
||||
class MediaVideo(Base, BaseMixin):
|
||||
__tablename__ = 'media_video'
|
||||
cloud_link = Column(String(255))
|
||||
file_name = Column(String(255))
|
||||
path = Column(String(255))
|
||||
review = Column(Boolean)
|
||||
title = Column(String(255))
|
||||
url = Column(String(255), unique=True)
|
||||
should_download = Column(Boolean)
|
||||
@@ -1,42 +0,0 @@
|
||||
from sqlalchemy import Column, String, ForeignKey, Integer
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
|
||||
|
||||
class MetaDataTable(Base, BaseMixin):
|
||||
__tablename__ = 'meta_data_table'
|
||||
table_name = Column(String(255), unique=True)
|
||||
table_columns = relationship("MetaDataColumn")
|
||||
|
||||
def __repr__(self):
|
||||
return f'MetaDataTable({self.id} {self.table_name})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.table_name}({self.id})'
|
||||
|
||||
|
||||
class MetaDataColumn(Base, BaseMixin):
|
||||
__tablename__ = 'meta_data_column'
|
||||
column_name = Column(String(255), nullable=False)
|
||||
column_sync_name = Column(String(255))
|
||||
column_type = Column(String(255))
|
||||
column_modifier = Column(String(255), nullable=True)
|
||||
column_order = Column(Integer)
|
||||
table_id = Column(String, ForeignKey('meta_data_table.id'))
|
||||
table = relationship("MetaDataTable", back_populates="table_columns")
|
||||
column_label = Column(String(255))
|
||||
filter_label = Column(String(255))
|
||||
is_shown = Column(BIT(1))
|
||||
show_filter = Column(BIT(1))
|
||||
ref_column = Column(String, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
if self.column_name is None:
|
||||
return f'MetaDataColumn({self.id} {self.table.table_name}.__)'
|
||||
else:
|
||||
return f'MetaDataColumn({self.id} {self.table.table_name}.{self.column_name})'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.column_name}({self.id})'
|
||||
@@ -1,100 +0,0 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
|
||||
|
||||
class Sport(Base, BaseMixin):
|
||||
__tablename__ = "sport"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name"),
|
||||
)
|
||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||
teams = relationship("Team")
|
||||
positions = relationship("FieldPosition")
|
||||
|
||||
|
||||
class Team(Base, BaseMixin):
|
||||
__tablename__ = "team"
|
||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||
short_name = Column(String(255), nullable=False, )
|
||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||
sport = relationship("Sport", back_populates="teams")
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
|
||||
class FieldPosition(Base, BaseMixin):
|
||||
__tablename__ = "field_position"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "sport_id"),
|
||||
UniqueConstraint("short_name", "sport_id"),
|
||||
)
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
short_name = Column(String(255), nullable=False)
|
||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
||||
sport = relationship("Sport", back_populates="positions")
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
|
||||
class Player(Base, BaseMixin):
|
||||
__tablename__ = "player"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("first_name", "last_name"),
|
||||
)
|
||||
first_name = Column(String(255), nullable=False, index=True)
|
||||
last_name = Column(String(255), nullable=False, index=True)
|
||||
roosters = relationship("Rooster")
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
return f"{self.last_name}, {self.first_name}"
|
||||
|
||||
|
||||
class Rooster(Base, BaseMixin):
|
||||
__tablename__ = "rooster"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("year", "team_id", "player_id", "position_id"),
|
||||
)
|
||||
year = Column(Integer)
|
||||
team_id = Column(String, ForeignKey("team.id"), nullable=False, index=True)
|
||||
team = relationship("Team", back_populates="roosters")
|
||||
player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
|
||||
player = relationship("Player", back_populates="roosters")
|
||||
position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
|
||||
position = relationship("FieldPosition", back_populates="roosters")
|
||||
cards = relationship("Card")
|
||||
|
||||
|
||||
class Vendor(Base, BaseMixin):
|
||||
__tablename__ = "vendor"
|
||||
name = Column(String(255), nullable=False, unique=True, index=True)
|
||||
card_sets = relationship("CardSet")
|
||||
cards = relationship("Card")
|
||||
|
||||
|
||||
class CardSet(Base, BaseMixin):
|
||||
__tablename__ = "card_set"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "vendor_id"),
|
||||
)
|
||||
name = Column(String(255), index=True)
|
||||
parallel_set = Column(BIT(1))
|
||||
insert_set = Column(BIT(1))
|
||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||
vendor = relationship("Vendor", back_populates="card_sets")
|
||||
cards = relationship("Card")
|
||||
|
||||
|
||||
class Card(Base, BaseMixin):
|
||||
__tablename__ = "card"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("card_number", "year", "vendor_id", "card_set_id"),
|
||||
)
|
||||
card_number = Column(Integer, index=True)
|
||||
year = Column(Integer, index=True)
|
||||
card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
|
||||
card_set = relationship("CardSet", back_populates="cards")
|
||||
rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
|
||||
rooster = relationship("Rooster", back_populates="cards")
|
||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
|
||||
vendor = relationship("Vendor", back_populates="cards")
|
||||
@@ -2,7 +2,6 @@
|
||||
download files with URLs from DB
|
||||
"""
|
||||
import logging.config
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
@@ -37,23 +36,33 @@ def get_logger(level: int, config: str):
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info('kontor.update_titles started')
|
||||
response = requests.get("http://127.0.0.1:8800/media/files?review=true")
|
||||
response = requests.get("http://127.0.0.1:8800/api/media/files?review=true")
|
||||
log.info(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
log.info(f"data: {len(data)}")
|
||||
for item in data:
|
||||
link = item['url']
|
||||
log.info(f"{item['id']} - {link}")
|
||||
try:
|
||||
r = requests.get(link)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
title = soup.title.string
|
||||
item['title'] = title
|
||||
item['review'] = 0
|
||||
except:
|
||||
item['title'] = None
|
||||
item['review'] = 1
|
||||
update = requests.put(f"http://127.0.0.1:8800/media/files/{item['id']}", json=item)
|
||||
log.info(f"{item['id']} - {str(link)}")
|
||||
if not link:
|
||||
continue
|
||||
if link == "None":
|
||||
item['url'] = None
|
||||
else:
|
||||
try:
|
||||
r = requests.get(link)
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
title_tag = soup.find('title')
|
||||
if title_tag:
|
||||
title= title_tag.get_text()
|
||||
title = soup.title.string
|
||||
item['title'] = title
|
||||
item['review'] = False
|
||||
except Exception as error:
|
||||
log.info(f"something went wrong: {error}")
|
||||
item['title'] = None
|
||||
item['review'] = True
|
||||
update = requests.put(f"http://127.0.0.1:8800/api/media/files/{item['id']}", json=item)
|
||||
log.info(f"update status: {update.status_code}")
|
||||
log.info(f"update result: {update.json()}")
|
||||
log.info('kontor.update_titles finished')
|
||||
|
||||
|
||||
Generated
+183
-15
@@ -46,6 +46,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload_time = "2024-09-04T20:45:21.852Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload_time = "2024-09-04T20:44:28.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload_time = "2024-09-04T20:44:30.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload_time = "2024-09-04T20:44:32.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload_time = "2024-09-04T20:44:33.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload_time = "2024-09-04T20:44:35.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload_time = "2024-09-04T20:44:36.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload_time = "2024-09-04T20:44:38.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload_time = "2024-09-04T20:44:40.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload_time = "2024-09-04T20:44:41.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload_time = "2024-09-04T20:44:43.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload_time = "2024-09-04T20:44:45.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.1"
|
||||
@@ -89,6 +111,35 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload_time = "2025-03-30T20:36:45.376Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload_time = "2025-03-30T20:35:47.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload_time = "2025-03-30T20:35:49.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload_time = "2025-03-30T20:35:51.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload_time = "2025-03-30T20:35:52.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload_time = "2025-03-30T20:35:54.658Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload_time = "2025-03-30T20:35:56.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload_time = "2025-03-30T20:35:57.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload_time = "2025-03-30T20:35:59.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload_time = "2025-03-30T20:36:01.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload_time = "2025-03-30T20:36:03.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload_time = "2025-03-30T20:36:04.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload_time = "2025-03-30T20:36:06.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload_time = "2025-03-30T20:36:08.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload_time = "2025-03-30T20:36:09.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload_time = "2025-03-30T20:36:11.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload_time = "2025-03-30T20:36:13.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload_time = "2025-03-30T20:36:16.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload_time = "2025-03-30T20:36:18.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload_time = "2025-03-30T20:36:19.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload_time = "2025-03-30T20:36:21.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload_time = "2025-03-30T20:36:43.61Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.7.0"
|
||||
@@ -98,6 +149,12 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload_time = "2024-10-05T20:14:57.687Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docopt"
|
||||
version = "0.6.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload_time = "2014-06-16T11:18:57.406Z" }
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
@@ -240,6 +297,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload_time = "2025-03-19T20:09:59.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload_time = "2025-03-19T20:10:01.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
@@ -258,40 +324,37 @@ version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "coverage" },
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "mariadb" },
|
||||
{ name = "pathlib" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "proton" },
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "python-qpid-proton" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "sqlmodel" },
|
||||
{ name = "stomp-py" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||
{ name = "coverage", specifier = ">=7.8.0" },
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
||||
{ name = "mariadb", specifier = ">=1.1.12" },
|
||||
{ name = "pathlib", specifier = ">=1.0.1" },
|
||||
{ name = "platformdirs", specifier = ">=4.3.7" },
|
||||
{ name = "proton", specifier = ">=0.9.1" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
|
||||
{ name = "pytest-cov", specifier = ">=6.1.1" },
|
||||
{ name = "python-qpid-proton", specifier = ">=0.40.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.40" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mariadb"
|
||||
version = "1.1.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/bb/4bbc803fbdafedbfba015f7cc1ab1e87a6d1de36725ba058c53e2f8a45ad/mariadb-1.1.12.tar.gz", hash = "sha256:50b02ff2c78b1b4f4628a054e3c8c7dd92972137727a5cc309a64c9ed20c878c", size = 85934, upload_time = "2025-02-13T13:11:48.642Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/1b/b6eca3870ac1b5577a10d3b49ba42ac263c2e5718c9224cc1c8463940422/mariadb-1.1.12-cp313-cp313-win32.whl", hash = "sha256:ba43c42130d41352f32a5786c339cc931d05472ef7640fa3764d428dc294b88e", size = 184338, upload_time = "2025-02-13T13:11:34.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/ff/c29a543ee1f9009755bc304138f61cd9b0ee1f14533e446513f84ccf143a/mariadb-1.1.12-cp313-cp313-win_amd64.whl", hash = "sha256:b69bc18418e72fcf359d17736cdc3f601a271203aff13ef7c57a415c8fd52ab0", size = 201272, upload_time = "2025-02-13T13:11:38.074Z" },
|
||||
{ name = "stomp-py" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -370,6 +433,49 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload_time = "2025-03-19T20:36:09.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload_time = "2024-04-20T21:34:42.531Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload_time = "2024-04-20T21:34:40.434Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proton"
|
||||
version = "0.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/2f/512c06db681c1cace7773b5615fab88b507b06b287c061d4b56fdeeafb4e/proton-0.9.1.tar.gz", hash = "sha256:aaea5dcbd3f57b4ef59207b92bc34c43e84be256822b4ea66daf55286c9f256f", size = 16983, upload_time = "2021-08-08T22:32:03.958Z" }
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2-binary"
|
||||
version = "2.9.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload_time = "2024-10-16T11:24:58.126Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload_time = "2024-10-16T11:21:42.841Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload_time = "2024-10-16T11:21:51.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload_time = "2024-10-16T11:21:57.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload_time = "2024-10-16T11:22:02.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload_time = "2024-10-16T11:22:06.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload_time = "2024-10-16T11:22:11.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload_time = "2024-10-16T11:22:16.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload_time = "2024-10-16T11:22:21.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload_time = "2024-10-16T11:22:25.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload_time = "2024-10-16T11:22:30.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload_time = "2025-01-04T20:09:19.234Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload_time = "2024-03-30T13:22:22.564Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload_time = "2024-03-30T13:22:20.476Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.3"
|
||||
@@ -422,6 +528,34 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload_time = "2025-01-06T17:26:25.553Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload_time = "2025-03-02T12:54:54.503Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload_time = "2025-03-02T12:54:52.069Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "6.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload_time = "2025-04-05T14:07:51.592Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload_time = "2025-04-05T14:07:49.641Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
@@ -440,6 +574,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload_time = "2024-12-16T19:45:44.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-qpid-proton"
|
||||
version = "0.40.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/dd/e9e5066009517bdfee92374264a2b6794fa0987bfeddcbf4d2a08dccaf36/python_qpid_proton-0.40.0.tar.gz", hash = "sha256:7680d607cf6e9684f97bf5b2ba16cda7d8512aab9e4ff78f98d44a4644fc819a", size = 354215, upload_time = "2025-05-19T18:45:37.932Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/dd/a82c1e377f08d62d83898c1aa9b39aef890e910f683fca6dc5242a123f6b/python_qpid_proton-0.40.0-cp313-cp313-win_amd64.whl", hash = "sha256:a19d8c71c908700ceb38f6cbc1eb4a039428570f96bfc2caeeafdfec804fb94f", size = 277376, upload_time = "2025-05-19T19:39:31.201Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
@@ -572,6 +718,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload_time = "2025-04-13T13:56:16.21Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stomp-py"
|
||||
version = "8.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docopt" },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/66/c07f01feb5fbc669c4333c76eb02fb8149c653c25ba9769477f8427d5e55/stomp_py-8.2.0.tar.gz", hash = "sha256:9908689361e263bf198e6acfb3c4386759fb7df7d141f4384d7414771c68d7fc", size = 39286, upload_time = "2024-10-31T21:59:38.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/b6/ebfd6daef0c19a5ca3ac1fb2fc092331d67af5a30c868f106fcc2504c287/stomp_py-8.2.0-py3-none-any.whl", hash = "sha256:fad24e51b505996015a39ca1632df4e0225c1c552980955e21f2aebfc0d9d85c", size = 42751, upload_time = "2024-10-31T21:59:36.658Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.15.2"
|
||||
@@ -678,6 +837,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload_time = "2025-04-08T10:35:52.458Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload_time = "2024-04-23T22:16:16.976Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload_time = "2024-04-23T22:16:14.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
|
||||
Reference in New Issue
Block a user