episoder 0.11.1__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- episoder-1.0.0/PKG-INFO +110 -0
- episoder-1.0.0/README.md +77 -0
- episoder-1.0.0/episoder/config.py +102 -0
- episoder-1.0.0/episoder/database.py +132 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder/episoder.py +61 -78
- episoder-1.0.0/episoder/main.py +377 -0
- episoder-1.0.0/episoder/options.py +176 -0
- episoder-1.0.0/episoder/output.py +153 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder/sources/__init__.py +1 -1
- {episoder-0.11.1 → episoder-1.0.0}/episoder/sources/epguides.py +10 -8
- {episoder-0.11.1 → episoder-1.0.0}/episoder/sources/parser.py +4 -3
- {episoder-0.11.1 → episoder-1.0.0}/episoder/sources/thetvdb.py +18 -18
- {episoder-0.11.1 → episoder-1.0.0}/episoder/sources/tvcom.py +1 -1
- {episoder-0.11.1 → episoder-1.0.0}/episoder/version.py +2 -2
- episoder-1.0.0/episoder.egg-info/PKG-INFO +110 -0
- episoder-1.0.0/episoder.egg-info/SOURCES.txt +39 -0
- episoder-1.0.0/episoder.egg-info/requires.txt +14 -0
- {episoder-0.11.1 → episoder-1.0.0}/setup.cfg +11 -1
- episoder-1.0.0/test/test_commands.py +220 -0
- episoder-1.0.0/test/test_config.py +135 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_database.py +140 -139
- {episoder-0.11.1 → episoder-1.0.0}/test/test_epguides_parser.py +113 -108
- {episoder-0.11.1 → episoder-1.0.0}/test/test_episode.py +12 -12
- episoder-1.0.0/test/test_main.py +911 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_migration.py +22 -22
- episoder-1.0.0/test/test_notify.py +129 -0
- episoder-1.0.0/test/test_options.py +427 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_output.py +61 -61
- {episoder-0.11.1 → episoder-1.0.0}/test/test_show.py +12 -12
- {episoder-0.11.1 → episoder-1.0.0}/test/test_tvdb.py +29 -29
- episoder-0.11.1/.gitignore +0 -2
- episoder-0.11.1/.gitlab-ci.yml +0 -124
- episoder-0.11.1/CHANGELOG +0 -222
- episoder-0.11.1/PKG-INFO +0 -71
- episoder-0.11.1/README.md +0 -47
- episoder-0.11.1/episoder/database.py +0 -134
- episoder-0.11.1/episoder/main.py +0 -449
- episoder-0.11.1/episoder/output.py +0 -169
- episoder-0.11.1/episoder.1 +0 -247
- episoder-0.11.1/episoder.egg-info/PKG-INFO +0 -71
- episoder-0.11.1/episoder.egg-info/SOURCES.txt +0 -76
- episoder-0.11.1/episoder.egg-info/requires.txt +0 -4
- episoder-0.11.1/examples/home.episoder +0 -30
- episoder-0.11.1/test/__init__.py +0 -0
- episoder-0.11.1/test/fixtures/epguides_48_hours_mistery.html +0 -399
- episoder-0.11.1/test/fixtures/epguides_american_idol.html +0 -872
- episoder-0.11.1/test/fixtures/epguides_bsg.html +0 -538
- episoder-0.11.1/test/fixtures/epguides_buzzcocks.html +0 -464
- episoder-0.11.1/test/fixtures/epguides_doctor_who.html +0 -842
- episoder-0.11.1/test/fixtures/epguides_eureka.html +0 -362
- episoder-0.11.1/test/fixtures/epguides_futurama.html +0 -407
- episoder-0.11.1/test/fixtures/epguides_house.html +0 -490
- episoder-0.11.1/test/fixtures/epguides_kr2008.html +0 -256
- episoder-0.11.1/test/fixtures/epguides_lost.html +0 -478
- episoder-0.11.1/test/fixtures/epguides_lost_html.html +0 -602
- episoder-0.11.1/test/fixtures/epguides_lower_decks.html +0 -340
- episoder-0.11.1/test/fixtures/epguides_midsomer_murders.html +0 -398
- episoder-0.11.1/test/fixtures/epguides_picard.html +0 -380
- episoder-0.11.1/test/fixtures/epguides_test_iso_8859_1.html +0 -138
- episoder-0.11.1/test/fixtures/tvdb_error_404.json +0 -3
- episoder-0.11.1/test/fixtures/tvdb_search_frasier.json +0 -10
- episoder-0.11.1/test/fixtures/tvdb_search_friends.json +0 -28
- episoder-0.11.1/test/fixtures/tvdb_test_show_260.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_260_1_eps.json +0 -19
- episoder-0.11.1/test/fixtures/tvdb_test_show_261.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_261_1_eps.json +0 -23
- episoder-0.11.1/test/fixtures/tvdb_test_show_261_2_eps.json +0 -23
- episoder-0.11.1/test/fixtures/tvdb_test_show_261_3_eps.json +0 -18
- episoder-0.11.1/test/fixtures/tvdb_test_show_262.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_262_1_eps.json +0 -20
- episoder-0.11.1/test/fixtures/tvdb_test_show_263.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_268156.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_268156_1_eps.json +0 -520
- episoder-0.11.1/test/fixtures/tvdb_test_show_295640.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_295640_1_eps.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_295640_2_eps.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_73739.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_73739_1_eps.json +0 -14
- episoder-0.11.1/test/fixtures/tvdb_test_show_75397.json +0 -1
- episoder-0.11.1/test/fixtures/tvdb_test_show_75397_1_eps.json +0 -1
- episoder-0.11.1/test/test_notify.py +0 -265
- episoder-0.11.1/test/util.py +0 -60
- {episoder-0.11.1 → episoder-1.0.0}/COPYING +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder/__init__.py +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder.egg-info/dependency_links.txt +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder.egg-info/entry_points.txt +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/episoder.egg-info/top_level.txt +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/pyproject.toml +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/setup.py +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_metainfo.py +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_sources.py +0 -0
- {episoder-0.11.1 → episoder-1.0.0}/test/test_tvcom.py +0 -0
episoder-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: episoder
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: TV episode notifier
|
|
5
|
+
Home-page: https://code.ott.net/episoder/
|
|
6
|
+
Author: Stefan Ott
|
|
7
|
+
Author-email: stefan@ott.net
|
|
8
|
+
Project-URL: Source, https://git.ott.net/episoder/
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Topic :: Multimedia :: Video
|
|
17
|
+
Requires-Python: >=3.6
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: COPYING
|
|
20
|
+
Requires-Dist: argparse
|
|
21
|
+
Requires-Dist: requests
|
|
22
|
+
Requires-Dist: beautifulsoup4
|
|
23
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: mypy; extra == "test"
|
|
26
|
+
Requires-Dist: flake8; extra == "test"
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: pyfakefs; extra == "test"
|
|
29
|
+
Requires-Dist: types-requests; extra == "test"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# episoder
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
Episoder is a command-line tool that tells you about new episodes of your
|
|
39
|
+
favourite TV shows. It gets its data from online TV episode guides such as
|
|
40
|
+
epguides.com and TheTVDB.
|
|
41
|
+
|
|
42
|
+
There is also a web frontend for episoder, called
|
|
43
|
+
[webisoder](https://code.ott.net/webisoder).
|
|
44
|
+
|
|
45
|
+
## Dependencies
|
|
46
|
+
|
|
47
|
+
In order to use episoder, you need to have Python installed on your system. In
|
|
48
|
+
addition to the default distribution, episoder uses the `requests`, `argparse`,
|
|
49
|
+
`sqlalchemy` and `beautifulsoup4` packages.
|
|
50
|
+
|
|
51
|
+
## Downloading
|
|
52
|
+
|
|
53
|
+
The official website, bug tracker etc. for episoder can be found at
|
|
54
|
+
[code.ott.net/episoder](https://code.ott.net/episoder/).
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
You can install episoder by running `pip install episoder` or you can download
|
|
59
|
+
the tarball from the official website, extract it and run `setup.py install`.
|
|
60
|
+
|
|
61
|
+
## Getting started
|
|
62
|
+
|
|
63
|
+
First, find a show you're interested in:
|
|
64
|
+
|
|
65
|
+
$ episoder search firefly
|
|
66
|
+
ID Name
|
|
67
|
+
------- --------------------
|
|
68
|
+
78874 Firefly
|
|
69
|
+
384248 Firefly Lane
|
|
70
|
+
329088 Firefly (2017)
|
|
71
|
+
|
|
72
|
+
Add that show to episoder:
|
|
73
|
+
|
|
74
|
+
$ episoder add 78874
|
|
75
|
+
|
|
76
|
+
Update your database:
|
|
77
|
+
|
|
78
|
+
$ episoder update
|
|
79
|
+
|
|
80
|
+
And finally show your episodes:
|
|
81
|
+
|
|
82
|
+
$ episoder list
|
|
83
|
+
|
|
84
|
+
The list may be empty, by default episoder deletes old episodes and will only
|
|
85
|
+
show you the next few days. The `--nodate` option to the `update` and `list`
|
|
86
|
+
command will change that, in case you care about days long gone and/or far in
|
|
87
|
+
the future.
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
The configuration file at `~/.episoder` contains your default settings for
|
|
92
|
+
episoder. After installing episoder, the man page will help you with the
|
|
93
|
+
configuration.
|
|
94
|
+
|
|
95
|
+
## Using episoder
|
|
96
|
+
|
|
97
|
+
To use episoder you will typically:
|
|
98
|
+
|
|
99
|
+
* Create a cron job to update your database once a day using `episoder update`
|
|
100
|
+
* Add `episoder list` to your `~/.bashrc`, `~/.bash_profile`, `/etc/bash.bashrc`
|
|
101
|
+
or `/etc/profile` if you want to see your upcoming shows every time you start
|
|
102
|
+
a shell. Consult your shell's documentation to find out which file to use.
|
|
103
|
+
* Or run `episoder notify` at regular intervals to get notifications by e-mail.
|
|
104
|
+
|
|
105
|
+
## More documentation
|
|
106
|
+
|
|
107
|
+
There is some documentation to help you get started:
|
|
108
|
+
|
|
109
|
+
* [Using episoder](https://code.ott.net/episoder/doc/use.html)
|
|
110
|
+
* [Episoder configuration](https://code.ott.net/episoder/doc/config.html)
|
episoder-1.0.0/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# episoder
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Episoder is a command-line tool that tells you about new episodes of your
|
|
6
|
+
favourite TV shows. It gets its data from online TV episode guides such as
|
|
7
|
+
epguides.com and TheTVDB.
|
|
8
|
+
|
|
9
|
+
There is also a web frontend for episoder, called
|
|
10
|
+
[webisoder](https://code.ott.net/webisoder).
|
|
11
|
+
|
|
12
|
+
## Dependencies
|
|
13
|
+
|
|
14
|
+
In order to use episoder, you need to have Python installed on your system. In
|
|
15
|
+
addition to the default distribution, episoder uses the `requests`, `argparse`,
|
|
16
|
+
`sqlalchemy` and `beautifulsoup4` packages.
|
|
17
|
+
|
|
18
|
+
## Downloading
|
|
19
|
+
|
|
20
|
+
The official website, bug tracker etc. for episoder can be found at
|
|
21
|
+
[code.ott.net/episoder](https://code.ott.net/episoder/).
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
You can install episoder by running `pip install episoder` or you can download
|
|
26
|
+
the tarball from the official website, extract it and run `setup.py install`.
|
|
27
|
+
|
|
28
|
+
## Getting started
|
|
29
|
+
|
|
30
|
+
First, find a show you're interested in:
|
|
31
|
+
|
|
32
|
+
$ episoder search firefly
|
|
33
|
+
ID Name
|
|
34
|
+
------- --------------------
|
|
35
|
+
78874 Firefly
|
|
36
|
+
384248 Firefly Lane
|
|
37
|
+
329088 Firefly (2017)
|
|
38
|
+
|
|
39
|
+
Add that show to episoder:
|
|
40
|
+
|
|
41
|
+
$ episoder add 78874
|
|
42
|
+
|
|
43
|
+
Update your database:
|
|
44
|
+
|
|
45
|
+
$ episoder update
|
|
46
|
+
|
|
47
|
+
And finally show your episodes:
|
|
48
|
+
|
|
49
|
+
$ episoder list
|
|
50
|
+
|
|
51
|
+
The list may be empty, by default episoder deletes old episodes and will only
|
|
52
|
+
show you the next few days. The `--nodate` option to the `update` and `list`
|
|
53
|
+
command will change that, in case you care about days long gone and/or far in
|
|
54
|
+
the future.
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
The configuration file at `~/.episoder` contains your default settings for
|
|
59
|
+
episoder. After installing episoder, the man page will help you with the
|
|
60
|
+
configuration.
|
|
61
|
+
|
|
62
|
+
## Using episoder
|
|
63
|
+
|
|
64
|
+
To use episoder you will typically:
|
|
65
|
+
|
|
66
|
+
* Create a cron job to update your database once a day using `episoder update`
|
|
67
|
+
* Add `episoder list` to your `~/.bashrc`, `~/.bash_profile`, `/etc/bash.bashrc`
|
|
68
|
+
or `/etc/profile` if you want to see your upcoming shows every time you start
|
|
69
|
+
a shell. Consult your shell's documentation to find out which file to use.
|
|
70
|
+
* Or run `episoder notify` at regular intervals to get notifications by e-mail.
|
|
71
|
+
|
|
72
|
+
## More documentation
|
|
73
|
+
|
|
74
|
+
There is some documentation to help you get started:
|
|
75
|
+
|
|
76
|
+
* [Using episoder](https://code.ott.net/episoder/doc/use.html)
|
|
77
|
+
* [Episoder configuration](https://code.ott.net/episoder/doc/config.html)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# episoder, https://code.ott.net/episoder
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2004-2026 Stefan Ott. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
from logging import getLogger
|
|
19
|
+
from os import environ, path
|
|
20
|
+
|
|
21
|
+
from typing import BinaryIO, Optional
|
|
22
|
+
|
|
23
|
+
from .version import __version__
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InvalidDataFormatError(Exception):
|
|
27
|
+
def __init__(self, filename: str) -> None:
|
|
28
|
+
super().__init__(f'Please remove old/invalid data file: {filename}')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EpisoderConfig:
|
|
32
|
+
# pylint: disable=too-few-public-methods,too-many-instance-attributes
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
self.agent = f'episoder/{__version__}'
|
|
35
|
+
self.datafile = path.join(environ['HOME'], '.episodes')
|
|
36
|
+
self.dateformat = '%a, %b %d, %Y'
|
|
37
|
+
self.format = '%airdate %show %seasonx%epnum'
|
|
38
|
+
self.tvdb_key = '8F15287C4B23B36E'
|
|
39
|
+
|
|
40
|
+
self.email_to: Optional[str] = None
|
|
41
|
+
self.email_username: Optional[str] = None
|
|
42
|
+
self.email_password: Optional[str] = None
|
|
43
|
+
self.email_server = 'localhost'
|
|
44
|
+
self.email_port = 587
|
|
45
|
+
self.email_tls = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ConfigLoader:
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self._log = getLogger('Config')
|
|
51
|
+
|
|
52
|
+
def _load_rcfile(self, file_: BinaryIO) -> dict[str, str]:
|
|
53
|
+
def strip_comments(line: str) -> str:
|
|
54
|
+
return line.split('#')[0]
|
|
55
|
+
|
|
56
|
+
def valid(line: str) -> bool:
|
|
57
|
+
return '=' in line
|
|
58
|
+
|
|
59
|
+
self._log.info('Loading config file')
|
|
60
|
+
lines = file_.readlines()
|
|
61
|
+
decoded_lines = [x.decode('utf8').strip() for x in lines]
|
|
62
|
+
non_comments = map(strip_comments, decoded_lines)
|
|
63
|
+
valid_lines = filter(valid, non_comments)
|
|
64
|
+
return dict(line.split('=') for line in valid_lines)
|
|
65
|
+
|
|
66
|
+
def _update(self, cfg: EpisoderConfig, values: dict[str, str]) -> None:
|
|
67
|
+
cfg.agent = values.get('agent', cfg.agent)
|
|
68
|
+
cfg.datafile = values.get('data', cfg.datafile)
|
|
69
|
+
cfg.dateformat = values.get('dateformat', cfg.dateformat)
|
|
70
|
+
cfg.format = values.get('format', cfg.format)
|
|
71
|
+
cfg.tvdb_key = values.get('tvdb_key', cfg.tvdb_key)
|
|
72
|
+
|
|
73
|
+
cfg.email_to = values.get('email_to', cfg.email_to)
|
|
74
|
+
cfg.email_username = values.get('email_username', cfg.email_username)
|
|
75
|
+
cfg.email_password = values.get('email_password', cfg.email_password)
|
|
76
|
+
cfg.email_port = int(values.get('email_port', cfg.email_port))
|
|
77
|
+
cfg.email_server = values.get('email_server', cfg.email_server)
|
|
78
|
+
cfg.email_tls = bool(values.get('email_tls', cfg.email_tls))
|
|
79
|
+
|
|
80
|
+
def load(self, file: str) -> EpisoderConfig:
|
|
81
|
+
config = EpisoderConfig()
|
|
82
|
+
|
|
83
|
+
if path.exists(file):
|
|
84
|
+
with open(file, 'rb') as file_:
|
|
85
|
+
values = self._load_rcfile(file_)
|
|
86
|
+
|
|
87
|
+
self._log.debug('Settings loaded: %s', values)
|
|
88
|
+
self._update(config, values)
|
|
89
|
+
self._log.info("Loaded configuration")
|
|
90
|
+
else:
|
|
91
|
+
self._log.warning('No config file found, using defaults')
|
|
92
|
+
|
|
93
|
+
return config
|
|
94
|
+
|
|
95
|
+
def validate(self, config: EpisoderConfig) -> None:
|
|
96
|
+
filename = config.datafile
|
|
97
|
+
if path.exists(filename):
|
|
98
|
+
with open(filename, 'rb') as file:
|
|
99
|
+
data = file.read(6)
|
|
100
|
+
|
|
101
|
+
if data != b'SQLite':
|
|
102
|
+
raise InvalidDataFormatError(filename)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# episoder, https://code.ott.net/episoder
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2004-2026 Stefan Ott. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
from datetime import date, datetime
|
|
19
|
+
from typing import Any, Optional
|
|
20
|
+
|
|
21
|
+
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, Text
|
|
22
|
+
from sqlalchemy import Sequence
|
|
23
|
+
from sqlalchemy.orm import backref, DeclarativeBase, relationship
|
|
24
|
+
from sqlalchemy.orm import mapped_column
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# pylint: disable=too-few-public-methods
|
|
28
|
+
class Base(DeclarativeBase):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Show(Base):
|
|
33
|
+
RUNNING = 1
|
|
34
|
+
SUSPENDED = 2
|
|
35
|
+
ENDED = 3
|
|
36
|
+
|
|
37
|
+
__tablename__ = "shows"
|
|
38
|
+
|
|
39
|
+
id = mapped_column("show_id", Integer, Sequence("shows_show_id_seq"),
|
|
40
|
+
primary_key=True)
|
|
41
|
+
name = mapped_column("show_name", Text)
|
|
42
|
+
url = mapped_column(Text, unique=True)
|
|
43
|
+
updated = mapped_column(DateTime)
|
|
44
|
+
enabled = mapped_column(Boolean)
|
|
45
|
+
status = mapped_column(Integer, default=RUNNING)
|
|
46
|
+
|
|
47
|
+
def __init__(self, name: str, url: str = "",
|
|
48
|
+
updated: datetime = datetime.fromtimestamp(0)) -> None:
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.name = name
|
|
51
|
+
self.url = url
|
|
52
|
+
self.updated = updated
|
|
53
|
+
self.status = Show.RUNNING
|
|
54
|
+
self.episodes: list[Episode] = []
|
|
55
|
+
self.enabled = True
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return f"Show: {self.name}"
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
return f'Show("{self.name}", "{self.url}")'
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other: object) -> Any:
|
|
64
|
+
if not isinstance(other, Show):
|
|
65
|
+
return NotImplemented
|
|
66
|
+
|
|
67
|
+
return self.name == other.name and self.url == other.url
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# pylint: disable=too-many-instance-attributes
|
|
71
|
+
class Episode(Base):
|
|
72
|
+
__tablename__ = "episodes"
|
|
73
|
+
|
|
74
|
+
show_id = mapped_column(Integer, ForeignKey(Show.id), primary_key=True)
|
|
75
|
+
episode = mapped_column("num", Integer, primary_key=True)
|
|
76
|
+
airdate = mapped_column(Date)
|
|
77
|
+
season = mapped_column(Integer, primary_key=True)
|
|
78
|
+
title = mapped_column(Text)
|
|
79
|
+
totalnum = mapped_column(Integer)
|
|
80
|
+
prodnum = mapped_column(Text)
|
|
81
|
+
notified = mapped_column(Date)
|
|
82
|
+
show = relationship(Show, backref=backref("episodes", cascade="all"))
|
|
83
|
+
|
|
84
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
85
|
+
def __init__(self, name: str, season: int, episode: int, aired: date,
|
|
86
|
+
prodnum: Optional[str], total: int) -> None:
|
|
87
|
+
super().__init__()
|
|
88
|
+
self.title = name
|
|
89
|
+
self.season = int(season)
|
|
90
|
+
self.episode = int(episode)
|
|
91
|
+
self.airdate = aired
|
|
92
|
+
self.prodnum = prodnum
|
|
93
|
+
self.totalnum = int(total)
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return f"{self.show.name} {self.season}x{self.episode:02d}: " \
|
|
97
|
+
f"{self.title}"
|
|
98
|
+
|
|
99
|
+
def __repr__(self) -> str:
|
|
100
|
+
airdate = self.airdate
|
|
101
|
+
|
|
102
|
+
return f'Episode("{self.title}", {self.season}, {self.episode}, ' \
|
|
103
|
+
f'date({airdate.year}, {airdate.month}, {airdate.day}), ' \
|
|
104
|
+
f'"{self.prodnum}", {self.totalnum})'
|
|
105
|
+
|
|
106
|
+
def __lt__(self, other: object) -> Any:
|
|
107
|
+
if not isinstance(other, Episode):
|
|
108
|
+
return NotImplemented
|
|
109
|
+
|
|
110
|
+
return ((self.season < other.season)
|
|
111
|
+
or ((self.season == other.season)
|
|
112
|
+
and (self.episode < other.episode)))
|
|
113
|
+
|
|
114
|
+
def __eq__(self, other: object) -> Any:
|
|
115
|
+
if not isinstance(other, Episode):
|
|
116
|
+
return NotImplemented
|
|
117
|
+
|
|
118
|
+
return (self.show_id == other.show_id
|
|
119
|
+
and self.season == other.season
|
|
120
|
+
and self.episode == other.episode)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Meta(Base):
|
|
124
|
+
__tablename__ = "meta"
|
|
125
|
+
key = mapped_column(Text, primary_key=True)
|
|
126
|
+
value = mapped_column(Text)
|
|
127
|
+
|
|
128
|
+
def __str__(self) -> str:
|
|
129
|
+
return f"Meta: {self.key} = {self.value}"
|
|
130
|
+
|
|
131
|
+
def __repr__(self) -> str:
|
|
132
|
+
return str(self)
|