carconnectivity-plugin-database 0.1a3__py3-none-any.whl
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.
- carconnectivity_database/__init__.py +0 -0
- carconnectivity_database/carconnectivity_database_base.py +26 -0
- carconnectivity_plugin_database-0.1a3.dist-info/METADATA +74 -0
- carconnectivity_plugin_database-0.1a3.dist-info/RECORD +34 -0
- carconnectivity_plugin_database-0.1a3.dist-info/WHEEL +5 -0
- carconnectivity_plugin_database-0.1a3.dist-info/entry_points.txt +2 -0
- carconnectivity_plugin_database-0.1a3.dist-info/licenses/LICENSE +21 -0
- carconnectivity_plugin_database-0.1a3.dist-info/top_level.txt +2 -0
- carconnectivity_plugins/database/__init__.py +0 -0
- carconnectivity_plugins/database/_version.py +34 -0
- carconnectivity_plugins/database/agents/base_agent.py +2 -0
- carconnectivity_plugins/database/agents/drive_state_agent.py +71 -0
- carconnectivity_plugins/database/agents/state_agent.py +94 -0
- carconnectivity_plugins/database/model/__init__.py +10 -0
- carconnectivity_plugins/database/model/alembic.ini +100 -0
- carconnectivity_plugins/database/model/base.py +4 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/README +1 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/__init__.py +0 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/env.py +78 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/script.py.mako +24 -0
- carconnectivity_plugins/database/model/carconnectivity_schema/versions/__init__.py +0 -0
- carconnectivity_plugins/database/model/connection_state.py +53 -0
- carconnectivity_plugins/database/model/datetime_decorator.py +44 -0
- carconnectivity_plugins/database/model/drive.py +87 -0
- carconnectivity_plugins/database/model/drive_level.py +46 -0
- carconnectivity_plugins/database/model/drive_range.py +49 -0
- carconnectivity_plugins/database/model/migrations.py +24 -0
- carconnectivity_plugins/database/model/outside_temperature.py +48 -0
- carconnectivity_plugins/database/model/state.py +52 -0
- carconnectivity_plugins/database/model/timedelta_decorator.py +33 -0
- carconnectivity_plugins/database/model/vehicle.py +148 -0
- carconnectivity_plugins/database/plugin.py +150 -0
- carconnectivity_plugins/database/ui/plugin_ui.py +48 -0
- carconnectivity_plugins/database/ui/templates/database/status.html +8 -0
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Module containing the commandline interface for the carconnectivity package."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from carconnectivity.carconnectivity_base import CLI
|
|
8
|
+
|
|
9
|
+
from carconnectivity_plugins.database._version import __version__
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
LOG = logging.getLogger("carconnectivity-database")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
"""
|
|
19
|
+
Entry point for the car connectivity database application.
|
|
20
|
+
|
|
21
|
+
This function initializes and starts the command-line interface (CLI) for the
|
|
22
|
+
car connectivity application using the specified logger and application name.
|
|
23
|
+
"""
|
|
24
|
+
cli: CLI = CLI(logger=LOG, name='carconnectivity-database', description='Commandline Interface to interact with Car Services of various brands',
|
|
25
|
+
subversion=__version__)
|
|
26
|
+
cli.main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: carconnectivity-plugin-database
|
|
3
|
+
Version: 0.1a3
|
|
4
|
+
Summary: CarConnectivity plugin for storing data to Databases
|
|
5
|
+
Author: Till Steinbach
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
9
|
+
Classifier: Intended Audience :: System Administrators
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Classifier: Topic :: Home Automation
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: carconnectivity>=0.9.2
|
|
23
|
+
Requires-Dist: sqlalchemy~=2.0.45
|
|
24
|
+
Requires-Dist: psycopg2-binary~=2.9.11
|
|
25
|
+
Requires-Dist: alembic~=1.17.2
|
|
26
|
+
Requires-Dist: SQLAlchemy-Utc~=0.14.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# CarConnectivity Plugin for Database storage
|
|
32
|
+
[](https://github.com/tillsteinbach/CarConnectivity-plugin-database/)
|
|
33
|
+
[](https://github.com/tillsteinbach/CarConnectivity-plugin-database/releases/latest)
|
|
34
|
+
[](https://github.com/tillsteinbach/CarConnectivity-plugin-database/blob/master/LICENSE)
|
|
35
|
+
[](https://github.com/tillsteinbach/CarConnectivity-plugin-database/issues)
|
|
36
|
+
[](https://pypi.org/project/carconnectivity-plugin-database/)
|
|
37
|
+
[](https://pypi.org/project/carconnectivity-plugin-database/)
|
|
38
|
+
[](https://www.paypal.com/donate?hosted_button_id=2BVFF5GJ9SXAJ)
|
|
39
|
+
[](https://github.com/sponsors/tillsteinbach)
|
|
40
|
+
|
|
41
|
+
[CarConnectivity](https://github.com/tillsteinbach/CarConnectivity) is a python API to connect to various car services. If you want to store the data collected from your vehicle to a relational database (e.g. MySQL, PostgreSQL, or SQLite) this plugin will help you.
|
|
42
|
+
|
|
43
|
+
### Install using PIP
|
|
44
|
+
If you want to use the CarConnectivity Plugin for Databases, the easiest way is to obtain it from [PyPI](https://pypi.org/project/carconnectivity-plugin-database/). Just install it using:
|
|
45
|
+
```bash
|
|
46
|
+
pip3 install carconnectivity-plugin-database
|
|
47
|
+
```
|
|
48
|
+
after you installed CarConnectivity
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
In your carconnectivity.json configuration add a section for the database plugin like this. A documentation of all possible config options can be found [here](https://github.com/tillsteinbach/CarConnectivity-plugin-database/tree/main/doc/Config.md).
|
|
52
|
+
```
|
|
53
|
+
{
|
|
54
|
+
"carConnectivity": {
|
|
55
|
+
"connectors": [
|
|
56
|
+
...
|
|
57
|
+
]
|
|
58
|
+
"plugins": [
|
|
59
|
+
{
|
|
60
|
+
"type": "database",
|
|
61
|
+
"config": {
|
|
62
|
+
"db_url": "sqlite:///carconnectivity.db"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Updates
|
|
71
|
+
If you want to update, the easiest way is:
|
|
72
|
+
```bash
|
|
73
|
+
pip3 install carconnectivity-plugin-database --upgrade
|
|
74
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
carconnectivity_database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
carconnectivity_database/carconnectivity_database_base.py,sha256=tsCWJJlBody2IDSw55WPkl-q4sAGNWHgid1iK7nQ5Jk,842
|
|
3
|
+
carconnectivity_plugin_database-0.1a3.dist-info/licenses/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
|
|
4
|
+
carconnectivity_plugins/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
carconnectivity_plugins/database/_version.py,sha256=_w2cIB58_wzKZ1MSbddFmQnkPRdfZ2IDpMt6GPDBSXg,707
|
|
6
|
+
carconnectivity_plugins/database/plugin.py,sha256=GoZDei--vp0oT5GIij1E3Pa0XA8OvRXugSjKcUOgoiw,7524
|
|
7
|
+
carconnectivity_plugins/database/agents/base_agent.py,sha256=WsEoB-dIZ6le3vuObwPDOBQ5WJUyfzqrE8SQIJD5X4I,28
|
|
8
|
+
carconnectivity_plugins/database/agents/drive_state_agent.py,sha256=k494_Zq8jgULjICnjiX1v5oZvgG9wApc1n5G5AQstdE,3877
|
|
9
|
+
carconnectivity_plugins/database/agents/state_agent.py,sha256=Cqu4SWrQdct6jYUSLz620uGwv_iEc-JYjIXLEaGABT4,6146
|
|
10
|
+
carconnectivity_plugins/database/model/__init__.py,sha256=dT11R2M29wQLbMLWtrBpgYfQCoYIvOKWSjO9DqrwSjk,426
|
|
11
|
+
carconnectivity_plugins/database/model/alembic.ini,sha256=_cT30ZkhLipi-fFq1NOPzBfpd_FTqj55co53TMuLGSM,2702
|
|
12
|
+
carconnectivity_plugins/database/model/base.py,sha256=bYej-MB6Xp4On2hQA3k5BRvwxz5frKjFLKX7Arq61HQ,147
|
|
13
|
+
carconnectivity_plugins/database/model/connection_state.py,sha256=QI6rPffEhsaYwU-Xsp6W1SDfJ1civWASbZzzxlebtiA,2547
|
|
14
|
+
carconnectivity_plugins/database/model/datetime_decorator.py,sha256=284bx5oQ5ACErm26qnif171QtKtF8efI2IBDXlyffzw,1444
|
|
15
|
+
carconnectivity_plugins/database/model/drive.py,sha256=f6Lu_SL9MdyczDRBHAgyae5FwQq4EAghcRlR0MGI0oA,4290
|
|
16
|
+
carconnectivity_plugins/database/model/drive_level.py,sha256=GbU8Tf2eMN6Y9ixKFkJX-vOHBf3FP-MXqaQA36_bySA,2035
|
|
17
|
+
carconnectivity_plugins/database/model/drive_range.py,sha256=6Rz-j6oFL0UAHHwNGJ0E3ST23sUa1fEgY9qSzoj40KY,2202
|
|
18
|
+
carconnectivity_plugins/database/model/migrations.py,sha256=x6XMnzTFfhIDRvEZ4B1jR73dFdrNUcpMCWeyAi324QI,897
|
|
19
|
+
carconnectivity_plugins/database/model/outside_temperature.py,sha256=kxqZk86rSa2eDkySmyUMRrHBdLMU5n1lQ2iePfUwS6k,2391
|
|
20
|
+
carconnectivity_plugins/database/model/state.py,sha256=rJQV-C4cGxbDZwyVaPo3RjbmeilGv5mPszdCX9YZmiM,2426
|
|
21
|
+
carconnectivity_plugins/database/model/timedelta_decorator.py,sha256=mdB9ZibJV-4v5H4EY25yxg407O2r6b5D960wMmCqLuE,976
|
|
22
|
+
carconnectivity_plugins/database/model/vehicle.py,sha256=NTlQhxzlt0UK2EgeNUZqg71G2qNoKNnGFWjedHAJGv0,8174
|
|
23
|
+
carconnectivity_plugins/database/model/carconnectivity_schema/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
|
|
24
|
+
carconnectivity_plugins/database/model/carconnectivity_schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
carconnectivity_plugins/database/model/carconnectivity_schema/env.py,sha256=trMAoTgw_EWjjTPnw1fWbKyDGEpAtbvd1exqznTozaE,2093
|
|
26
|
+
carconnectivity_plugins/database/model/carconnectivity_schema/script.py.mako,sha256=8_xgA-gm_OhehnO7CiIijWgnm00ZlszEHtIHrAYFJl0,494
|
|
27
|
+
carconnectivity_plugins/database/model/carconnectivity_schema/versions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
carconnectivity_plugins/database/ui/plugin_ui.py,sha256=LQOuXIeBOLH4Gg0Qqjmzu9BOHI0Rir9KLq3gZF5Rlr4,1815
|
|
29
|
+
carconnectivity_plugins/database/ui/templates/database/status.html,sha256=OypmDrRib6tBM1fC7ijyTmF9HI53XlpF4tq_diXiLKc,162
|
|
30
|
+
carconnectivity_plugin_database-0.1a3.dist-info/METADATA,sha256=favJKw69RkxAnQQOwygUaTMJXIzX0wB-zimOHcruOxU,3625
|
|
31
|
+
carconnectivity_plugin_database-0.1a3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
32
|
+
carconnectivity_plugin_database-0.1a3.dist-info/entry_points.txt,sha256=YB5RERxEA_pxYQesyAyzV8hmPM2o3_F9FZbnSQaSuLQ,105
|
|
33
|
+
carconnectivity_plugin_database-0.1a3.dist-info/top_level.txt,sha256=836FhYp_OWsQq5Vyjb95dilEMQPHUYCTZa2GBgNFEVE,49
|
|
34
|
+
carconnectivity_plugin_database-0.1a3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Till Steinbach
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1a3'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 'a3')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from carconnectivity.observable import Observable
|
|
5
|
+
|
|
6
|
+
from carconnectivity_plugins.database.agents.base_agent import BaseAgent
|
|
7
|
+
from carconnectivity_plugins.database.model.drive_level import DriveLevel
|
|
8
|
+
from carconnectivity_plugins.database.model.drive_range import DriveRange
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from sqlalchemy.orm.session import Session
|
|
13
|
+
|
|
14
|
+
from carconnectivity.attributes import LevelAttribute, RangeAttribute
|
|
15
|
+
|
|
16
|
+
from carconnectivity_plugins.database.model.drive import Drive
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DriveStateAgent(BaseAgent):
|
|
20
|
+
def __init__(self, session: Session, drive: Drive) -> None:
|
|
21
|
+
if drive is None or drive.carconnectivity_drive is None:
|
|
22
|
+
raise ValueError("Drive or its carconnectivity_drive attribute is None")
|
|
23
|
+
self.session: Session = session
|
|
24
|
+
self.drive: Drive = drive
|
|
25
|
+
|
|
26
|
+
self.last_level: Optional[DriveLevel] = session.query(DriveLevel).filter(DriveLevel.drive_id == drive.id).order_by(DriveLevel.first_date.desc()).first()
|
|
27
|
+
self.last_range: Optional[DriveRange] = session.query(DriveRange).filter(DriveRange.drive_id == drive.id).order_by(DriveRange.first_date.desc()).first()
|
|
28
|
+
|
|
29
|
+
drive.carconnectivity_drive.level.add_observer(self.__on_level_change, Observable.ObserverEvent.UPDATED)
|
|
30
|
+
self.__on_level_change(drive.carconnectivity_drive.level, Observable.ObserverEvent.UPDATED)
|
|
31
|
+
|
|
32
|
+
drive.carconnectivity_drive.range.add_observer(self.__on_range_change, Observable.ObserverEvent.UPDATED)
|
|
33
|
+
self.__on_range_change(drive.carconnectivity_drive.range, Observable.ObserverEvent.UPDATED)
|
|
34
|
+
|
|
35
|
+
def __on_level_change(self, element: LevelAttribute, flags: Observable.ObserverEvent) -> None:
|
|
36
|
+
del flags
|
|
37
|
+
if self.last_level is not None:
|
|
38
|
+
self.session.refresh(self.last_level)
|
|
39
|
+
if (self.last_level is None or self.last_level.level != element.value) \
|
|
40
|
+
and element.last_updated is not None:
|
|
41
|
+
new_level: DriveLevel = DriveLevel(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
|
|
42
|
+
level=element.value)
|
|
43
|
+
with self.session.begin_nested():
|
|
44
|
+
self.session.add(new_level)
|
|
45
|
+
self.session.commit()
|
|
46
|
+
self.last_level = new_level
|
|
47
|
+
elif self.last_level is not None and self.last_level.level == element.value \
|
|
48
|
+
and element.last_updated is not None:
|
|
49
|
+
if self.last_level.last_date is None or element.last_updated > self.last_level.last_date:
|
|
50
|
+
with self.session.begin_nested():
|
|
51
|
+
self.last_level.last_date = element.last_updated
|
|
52
|
+
self.session.commit()
|
|
53
|
+
|
|
54
|
+
def __on_range_change(self, element: RangeAttribute, flags: Observable.ObserverEvent) -> None:
|
|
55
|
+
del flags
|
|
56
|
+
if self.last_range is not None:
|
|
57
|
+
self.session.refresh(self.last_range)
|
|
58
|
+
if (self.last_range is None or self.last_range.range != element.value) \
|
|
59
|
+
and element.last_updated is not None:
|
|
60
|
+
new_range: DriveRange = DriveRange(drive_id=self.drive.id, first_date=element.last_updated, last_date=element.last_updated,
|
|
61
|
+
range=element.value)
|
|
62
|
+
with self.session.begin_nested():
|
|
63
|
+
self.session.add(new_range)
|
|
64
|
+
self.session.commit()
|
|
65
|
+
self.last_range = new_range
|
|
66
|
+
elif self.last_range is not None and self.last_range.range == element.value \
|
|
67
|
+
and element.last_updated is not None:
|
|
68
|
+
if self.last_range.last_date is None or element.last_updated > self.last_range.last_date:
|
|
69
|
+
with self.session.begin_nested():
|
|
70
|
+
self.last_range.last_date = element.last_updated
|
|
71
|
+
self.session.commit()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from carconnectivity.observable import Observable
|
|
5
|
+
|
|
6
|
+
from carconnectivity_plugins.database.agents.base_agent import BaseAgent
|
|
7
|
+
from carconnectivity_plugins.database.model.state import State
|
|
8
|
+
from carconnectivity_plugins.database.model.connection_state import ConnectionState
|
|
9
|
+
from carconnectivity_plugins.database.model.outside_temperature import OutsideTemperature
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from sqlalchemy.orm.session import Session
|
|
14
|
+
|
|
15
|
+
from carconnectivity.attributes import EnumAttribute, TemperatureAttribute
|
|
16
|
+
|
|
17
|
+
from carconnectivity.vehicle import GenericVehicle
|
|
18
|
+
|
|
19
|
+
from carconnectivity_plugins.database.model.vehicle import Vehicle
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StateAgent(BaseAgent):
|
|
23
|
+
def __init__(self, session: Session, vehicle: Vehicle) -> None:
|
|
24
|
+
if vehicle is None or vehicle.carconnectivity_vehicle is None:
|
|
25
|
+
raise ValueError("Vehicle or its carconnectivity_vehicle attribute is None")
|
|
26
|
+
self.session: Session = session
|
|
27
|
+
self.vehicle: Vehicle = vehicle
|
|
28
|
+
self.last_state: Optional[State] = session.query(State).filter(State.vehicle == vehicle).order_by(State.first_date.desc()).first()
|
|
29
|
+
self.last_connection_state: Optional[ConnectionState] = session.query(ConnectionState).filter(ConnectionState.vehicle == vehicle) \
|
|
30
|
+
.order_by(ConnectionState.first_date.desc()).first()
|
|
31
|
+
self.last_outside_temperature: Optional[OutsideTemperature] = session.query(OutsideTemperature).filter(OutsideTemperature.vehicle == vehicle) \
|
|
32
|
+
.order_by(OutsideTemperature.first_date.desc()).first()
|
|
33
|
+
|
|
34
|
+
vehicle.carconnectivity_vehicle.state.add_observer(self.__on_state_change, Observable.ObserverEvent.UPDATED)
|
|
35
|
+
self.__on_state_change(vehicle.carconnectivity_vehicle.state, Observable.ObserverEvent.UPDATED)
|
|
36
|
+
|
|
37
|
+
vehicle.carconnectivity_vehicle.connection_state.add_observer(self.__on_connection_state_change, Observable.ObserverEvent.UPDATED)
|
|
38
|
+
self.__on_connection_state_change(vehicle.carconnectivity_vehicle.connection_state, Observable.ObserverEvent.UPDATED)
|
|
39
|
+
|
|
40
|
+
vehicle.carconnectivity_vehicle.outside_temperature.add_observer(self.__on_outside_temperature_change, Observable.ObserverEvent.UPDATED)
|
|
41
|
+
self.__on_outside_temperature_change(vehicle.carconnectivity_vehicle.outside_temperature, Observable.ObserverEvent.UPDATED)
|
|
42
|
+
|
|
43
|
+
def __on_state_change(self, element: EnumAttribute[GenericVehicle.State], flags: Observable.ObserverEvent) -> None:
|
|
44
|
+
del flags
|
|
45
|
+
if self.last_state is not None:
|
|
46
|
+
self.session.refresh(self.last_state)
|
|
47
|
+
if (self.last_state is None or self.last_state.state != element.value) \
|
|
48
|
+
and element.last_updated is not None:
|
|
49
|
+
new_state: State = State(vin=self.vehicle.vin, first_date=element.last_updated, last_date=element.last_updated, state=element.value)
|
|
50
|
+
with self.session.begin_nested():
|
|
51
|
+
self.session.add(new_state)
|
|
52
|
+
self.session.commit()
|
|
53
|
+
self.last_state = new_state
|
|
54
|
+
elif self.last_state is not None and self.last_state.state == element.value and element.last_updated is not None:
|
|
55
|
+
if self.last_state.last_date is None or element.last_updated > self.last_state.last_date:
|
|
56
|
+
with self.session.begin_nested():
|
|
57
|
+
self.last_state.last_date = element.last_updated
|
|
58
|
+
self.session.commit()
|
|
59
|
+
|
|
60
|
+
def __on_connection_state_change(self, element: EnumAttribute[GenericVehicle.ConnectionState], flags: Observable.ObserverEvent) -> None:
|
|
61
|
+
del flags
|
|
62
|
+
if self.last_connection_state is not None:
|
|
63
|
+
self.session.refresh(self.last_connection_state)
|
|
64
|
+
if (self.last_connection_state is None or self.last_connection_state.connection_state != element.value) \
|
|
65
|
+
and element.last_updated is not None:
|
|
66
|
+
new_connection_state: ConnectionState = ConnectionState(vin=self.vehicle.vin, first_date=element.last_updated,
|
|
67
|
+
last_date=element.last_updated, connection_state=element.value)
|
|
68
|
+
with self.session.begin_nested():
|
|
69
|
+
self.session.add(new_connection_state)
|
|
70
|
+
self.session.commit()
|
|
71
|
+
self.last_connection_state = new_connection_state
|
|
72
|
+
elif self.last_connection_state is not None and self.last_connection_state.connection_state == element.value and element.last_updated is not None:
|
|
73
|
+
if self.last_connection_state.last_date is None or element.last_updated > self.last_connection_state.last_date:
|
|
74
|
+
with self.session.begin_nested():
|
|
75
|
+
self.last_connection_state.last_date = element.last_updated
|
|
76
|
+
self.session.commit()
|
|
77
|
+
|
|
78
|
+
def __on_outside_temperature_change(self, element: TemperatureAttribute, flags: Observable.ObserverEvent) -> None:
|
|
79
|
+
del flags
|
|
80
|
+
if self.last_outside_temperature is not None:
|
|
81
|
+
self.session.refresh(self.last_outside_temperature)
|
|
82
|
+
if (self.last_outside_temperature is None or self.last_outside_temperature.outside_temperature != element.value) \
|
|
83
|
+
and element.last_updated is not None:
|
|
84
|
+
new_outside_temperature: OutsideTemperature = OutsideTemperature(vin=self.vehicle.vin, first_date=element.last_updated,
|
|
85
|
+
last_date=element.last_updated, outside_temperature=element.value)
|
|
86
|
+
self.session.add(new_outside_temperature)
|
|
87
|
+
self.session.commit()
|
|
88
|
+
self.last_outside_temperature = new_outside_temperature
|
|
89
|
+
elif self.last_outside_temperature is not None and self.last_outside_temperature.outside_temperature == element.value \
|
|
90
|
+
and element.last_updated is not None:
|
|
91
|
+
if self.last_outside_temperature.last_date is None or element.last_updated > self.last_outside_temperature.last_date:
|
|
92
|
+
with self.session.begin_nested():
|
|
93
|
+
self.last_outside_temperature.last_date = element.last_updated
|
|
94
|
+
self.session.commit()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
""" This module contains the database model for car connectivity plugins."""
|
|
2
|
+
|
|
3
|
+
from .drive import Drive # noqa: F401
|
|
4
|
+
from .drive_level import DriveLevel # noqa: F401
|
|
5
|
+
from .drive_range import DriveRange # noqa: F401
|
|
6
|
+
from .connection_state import ConnectionState # noqa: F401
|
|
7
|
+
from .outside_temperature import OutsideTemperature # noqa: F401
|
|
8
|
+
from .state import State # noqa: F401
|
|
9
|
+
from .vehicle import Vehicle # noqa: F401
|
|
10
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# A generic, single database configuration.
|
|
2
|
+
|
|
3
|
+
[alembic]
|
|
4
|
+
# path to migration scripts
|
|
5
|
+
script_location = carconnectivity_schema
|
|
6
|
+
|
|
7
|
+
# template used to generate migration files
|
|
8
|
+
# file_template = %%(rev)s_%%(slug)s
|
|
9
|
+
|
|
10
|
+
# sys.path path, will be prepended to sys.path if present.
|
|
11
|
+
# defaults to the current working directory.
|
|
12
|
+
prepend_sys_path = .
|
|
13
|
+
|
|
14
|
+
# timezone to use when rendering the date within the migration file
|
|
15
|
+
# as well as the filename.
|
|
16
|
+
# If specified, requires the python-dateutil library that can be
|
|
17
|
+
# installed by adding `alembic[tz]` to the pip requirements
|
|
18
|
+
# string value is passed to dateutil.tz.gettz()
|
|
19
|
+
# leave blank for localtime
|
|
20
|
+
# timezone =
|
|
21
|
+
|
|
22
|
+
# max length of characters to apply to the
|
|
23
|
+
# "slug" field
|
|
24
|
+
# truncate_slug_length = 40
|
|
25
|
+
|
|
26
|
+
# set to 'true' to run the environment during
|
|
27
|
+
# the 'revision' command, regardless of autogenerate
|
|
28
|
+
# revision_environment = false
|
|
29
|
+
|
|
30
|
+
# set to 'true' to allow .pyc and .pyo files without
|
|
31
|
+
# a source .py file to be detected as revisions in the
|
|
32
|
+
# versions/ directory
|
|
33
|
+
# sourceless = false
|
|
34
|
+
|
|
35
|
+
# version location specification; This defaults
|
|
36
|
+
# to vwsfriend-schema/versions. When using multiple version
|
|
37
|
+
# directories, initial revisions must be specified with --version-path.
|
|
38
|
+
# The path separator used here should be the separator specified by "version_path_separator"
|
|
39
|
+
# version_locations = %(here)s/bar:%(here)s/bat:vwsfriend-schema/versions
|
|
40
|
+
|
|
41
|
+
# version path separator; As mentioned above, this is the character used to split
|
|
42
|
+
# version_locations. Valid values are:
|
|
43
|
+
#
|
|
44
|
+
# version_path_separator = :
|
|
45
|
+
# version_path_separator = ;
|
|
46
|
+
# version_path_separator = space
|
|
47
|
+
version_path_separator = os # default: use os.pathsep
|
|
48
|
+
|
|
49
|
+
# the output encoding used when revision files
|
|
50
|
+
# are written from script.py.mako
|
|
51
|
+
# output_encoding = utf-8
|
|
52
|
+
|
|
53
|
+
sqlalchemy.url = sqlite:///carconnectivity-schema/reference.db
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
[post_write_hooks]
|
|
57
|
+
# post_write_hooks defines scripts or Python functions that are run
|
|
58
|
+
# on newly generated revision scripts. See the documentation for further
|
|
59
|
+
# detail and examples
|
|
60
|
+
|
|
61
|
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
|
62
|
+
# hooks = black
|
|
63
|
+
# black.type = console_scripts
|
|
64
|
+
# black.entrypoint = black
|
|
65
|
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
|
66
|
+
|
|
67
|
+
# Logging configuration
|
|
68
|
+
[loggers]
|
|
69
|
+
keys = root,sqlalchemy,alembic
|
|
70
|
+
|
|
71
|
+
[handlers]
|
|
72
|
+
keys = console
|
|
73
|
+
|
|
74
|
+
[formatters]
|
|
75
|
+
keys = generic
|
|
76
|
+
|
|
77
|
+
[logger_root]
|
|
78
|
+
level = WARN
|
|
79
|
+
handlers = console
|
|
80
|
+
qualname =
|
|
81
|
+
|
|
82
|
+
[logger_sqlalchemy]
|
|
83
|
+
level = WARN
|
|
84
|
+
handlers =
|
|
85
|
+
qualname = sqlalchemy.engine
|
|
86
|
+
|
|
87
|
+
[logger_alembic]
|
|
88
|
+
level = INFO
|
|
89
|
+
handlers =
|
|
90
|
+
qualname = alembic
|
|
91
|
+
|
|
92
|
+
[handler_console]
|
|
93
|
+
class = StreamHandler
|
|
94
|
+
args = (sys.stderr,)
|
|
95
|
+
level = NOTSET
|
|
96
|
+
formatter = generic
|
|
97
|
+
|
|
98
|
+
[formatter_generic]
|
|
99
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
100
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Generic single-database configuration.
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# pylint: skip-file
|
|
2
|
+
from logging.config import fileConfig
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import engine_from_config
|
|
5
|
+
from sqlalchemy import pool
|
|
6
|
+
|
|
7
|
+
from alembic import context
|
|
8
|
+
|
|
9
|
+
from carconnectivity_plugins.database.model.base import Base
|
|
10
|
+
|
|
11
|
+
# this is the Alembic Config object, which provides
|
|
12
|
+
# access to the values within the .ini file in use.
|
|
13
|
+
config = context.config
|
|
14
|
+
|
|
15
|
+
# Interpret the config file for Python logging.
|
|
16
|
+
# This line sets up loggers basically.
|
|
17
|
+
if config.attributes.get('configure_logger', True):
|
|
18
|
+
fileConfig(config.config_file_name)
|
|
19
|
+
|
|
20
|
+
target_metadata = Base.metadata
|
|
21
|
+
|
|
22
|
+
# other values from the config, defined by the needs of env.py,
|
|
23
|
+
# can be acquired:
|
|
24
|
+
# my_important_option = config.get_main_option("my_important_option")
|
|
25
|
+
# ... etc.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_migrations_offline():
|
|
29
|
+
"""Run migrations in 'offline' mode.
|
|
30
|
+
|
|
31
|
+
This configures the context with just a URL
|
|
32
|
+
and not an Engine, though an Engine is acceptable
|
|
33
|
+
here as well. By skipping the Engine creation
|
|
34
|
+
we don't even need a DBAPI to be available.
|
|
35
|
+
|
|
36
|
+
Calls to context.execute() here emit the given string to the
|
|
37
|
+
script output.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
41
|
+
context.configure(
|
|
42
|
+
url=url,
|
|
43
|
+
target_metadata=target_metadata,
|
|
44
|
+
literal_binds=True,
|
|
45
|
+
dialect_opts={"paramstyle": "named"},
|
|
46
|
+
compare_type=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
with context.begin_transaction():
|
|
50
|
+
context.run_migrations()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_migrations_online():
|
|
54
|
+
"""Run migrations in 'online' mode.
|
|
55
|
+
|
|
56
|
+
In this scenario we need to create an Engine
|
|
57
|
+
and associate a connection with the context.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
connectable = engine_from_config(
|
|
61
|
+
config.get_section(config.config_ini_section),
|
|
62
|
+
prefix="sqlalchemy.",
|
|
63
|
+
poolclass=pool.NullPool,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with connectable.connect() as connection:
|
|
67
|
+
context.configure(
|
|
68
|
+
connection=connection, target_metadata=target_metadata, compare_type=True
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
with context.begin_transaction():
|
|
72
|
+
context.run_migrations()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if context.is_offline_mode():
|
|
76
|
+
run_migrations_offline()
|
|
77
|
+
else:
|
|
78
|
+
run_migrations_online()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
${imports if imports else ""}
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = ${repr(up_revision)}
|
|
14
|
+
down_revision = ${repr(down_revision)}
|
|
15
|
+
branch_labels = ${repr(branch_labels)}
|
|
16
|
+
depends_on = ${repr(depends_on)}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade():
|
|
20
|
+
${upgrades if upgrades else "pass"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def downgrade():
|
|
24
|
+
${downgrades if downgrades else "pass"}
|
|
File without changes
|