Compare commits

..

9 Commits

Author SHA1 Message Date
John Lancaster
7ba1abec84 pyright config 2025-06-20 08:50:54 -05:00
John Lancaster
475bdb9dd9 simple fixes 2025-06-20 08:50:43 -05:00
John Lancaster
ba043554dc linting 2025-06-20 08:30:46 -05:00
John Lancaster
bd5c7ee339 updated ruff rules 2025-06-20 08:29:28 -05:00
John Lancaster
ce4ede51b1 removed hello stub 2025-06-20 08:21:28 -05:00
John Lancaster
edee038a9f added ruff.toml 2025-06-20 08:21:01 -05:00
John Lancaster
7025bcde99 changed to uv 2025-06-20 08:20:50 -05:00
John Lancaster
ad34a6e919 cleanup 2025-06-20 08:20:26 -05:00
John Lancaster
99936f2c85 cleaned up simple 2025-06-20 08:20:19 -05:00
21 changed files with 118 additions and 138 deletions

8
.gitignore vendored
View File

@@ -4,7 +4,15 @@ __pycache__
.python-version .python-version
.venv .venv
conf/compiled
conf/dashboards
conf/namespaces
conf/www
conf/appdaemon.yaml conf/appdaemon.yaml
conf/secrets.yaml conf/secrets.yaml
log log
*.log
*.db
*.js
*cache*

View File

@@ -4,5 +4,4 @@ logger = logging.getLogger('AppDaemon.Perimeter')
logger.info('Imported statemachine') logger.info('Imported statemachine')
class StateMachine: class StateMachine: ...
...

View File

@@ -5,8 +5,8 @@ from dataclasses import dataclass
class Rule1: class Rule1:
state: str state: str
@dataclass @dataclass
class Rule2: class Rule2:
value: int value: int
other: str = "default" # test changing this other: str = 'default' # test changing this

View File

@@ -2,5 +2,5 @@ from appdaemon.adapi import ADAPI
class GrandParent(ADAPI): class GrandParent(ADAPI):
def initialize(self): def initialize(self) -> None:
self.log(f'{self.__class__.__name__} Initialized') self.log(f'{self.__class__.__name__} Initialized')

View File

@@ -2,5 +2,5 @@ from appdaemon.adapi import ADAPI
class Parent(ADAPI): class Parent(ADAPI):
def initialize(self): def initialize(self) -> None:
self.log(f'{self.__class__.__name__} Initialized') self.log(f'{self.__class__.__name__} Initialized')

View File

@@ -1,7 +1,6 @@
from child import Child from child import Child
class Sibling(Child): class Sibling(Child):
def initialize(self): def initialize(self) -> None:
self.log(f'{self.__class__.__name__} Initialized') self.log(f'{self.__class__.__name__} Initialized')

View File

@@ -1,5 +1,4 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List
from .base import FoodItem from .base import FoodItem
from .eggs import Eggs from .eggs import Eggs
@@ -12,7 +11,7 @@ class GreenEggs(Eggs):
@dataclass @dataclass
class Menu: class Menu:
dishes: List[type[FoodItem]] = field(init=False) dishes: list[type[FoodItem]] = field(init=False)
def __post_init__(self): def __post_init__(self):
self.dishes = [GreenEggs, Ham] self.dishes = [GreenEggs, Ham]

View File

@@ -13,4 +13,5 @@ class Utensil(Enum):
POT = auto() POT = auto()
PAN = auto() PAN = auto()
GLOBAL_VAR = Utensil.FORK GLOBAL_VAR = Utensil.FORK

View File

@@ -5,7 +5,7 @@ from food.menu import Menu
class Restaurant(ADAPI): class Restaurant(ADAPI):
menu: Menu menu: Menu
def initialize(self): def initialize(self) -> None:
self.log(f'{self.__class__.__name__} initialized') self.log(f'{self.__class__.__name__} initialized')
self.menu = Menu() self.menu = Menu()

View File

@@ -1,18 +1,22 @@
import logging
from enum import Enum from enum import Enum
GLOBAL_VAR = "Hello, World!" LOGGER = logging.getLogger('AppDaemon._globals')
def global_function(): GLOBAL_VAR = 'Hello, World!'
print('This is a global function.')
def global_function() -> None:
LOGGER.info('This is a global function.')
class GlobalClass: class GlobalClass:
def __init__(self): def __init__(self) -> None:
self.value = 'This is a global class instance.' self.value = 'This is a global class instance.'
def display(self): def display(self) -> None:
print(self.value) LOGGER.info(self.value)
class ModeSelect(Enum): class ModeSelect(Enum):

View File

@@ -3,8 +3,9 @@ from typing import Any
logger = logging.getLogger('AppDaemon.Perimeter') logger = logging.getLogger('AppDaemon.Perimeter')
class HAL: class HAL:
def __init__(self, *args: Any, **kwargs: Any): def __init__(self, *args: Any, **kwargs: Any) -> None:
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
logger.info('Logging from HAL') logger.info('Logging from HAL')

View File

@@ -1,8 +1,9 @@
CONSTANTS = { CONSTANTS = {
'A': 1, 'A': 1,
'B': 2, 'B': 2,
'C': 3 'C': 3,
} }
def utility_function(): def utility_function():
return 123, 456 return 123, 456

View File

@@ -12,17 +12,16 @@ simple_app:
# dependencies: # dependencies:
# - hello_world # - hello_world
base_app: base_app:
module: simple module: simple
class: BaseApp class: BaseApp
AppA: # AppA:
module: app_a # module: app_a
class: AppA # class: AppA
dependencies: # dependencies:
- AppB # This is only set to demonstrate forcing it to load after AppB # - AppB # This is only set to demonstrate forcing it to load after AppB
AppB: # AppB:
module: app_b # module: app_b
class: AppB # class: AppB

View File

@@ -10,14 +10,15 @@ from appdaemon.adapi import ADAPI
# fake/ # fake/
# SimpleApp # SimpleApp
class HelloWorld(ADAPI): class HelloWorld(ADAPI):
def initialize(self): def initialize(self) -> None:
self.log(f'{self.__class__.__name__} Initialized') self.log(f'{self.__class__.__name__} Initialized')
self.log('+' * 50) self.log('+' * 50)
# fake # fake
self.register_service("my_domain/my_exciting_service", self.my_exciting_cb) self.register_service('my_domain/my_exciting_service', self.my_exciting_cb)
def my_exciting_cb(self, *args: str, my_arg: int = 0, **kwargs: Any) -> Any: def my_exciting_cb(self, *args: str, my_arg: int = 0, **kwargs: Any) -> Any:
namespace, domain, service = args namespace, domain, service = args
self.log(f"Service {domain}/{service} in the {namespace} namepsace called with {kwargs}") self.log(f'Service {domain}/{service} in the {namespace} namepsace called with {kwargs}')
return 999 + my_arg return 999 + my_arg

View File

@@ -1,18 +1,10 @@
from datetime import datetime
from typing import Any
from appdaemon import adbase as ad
from appdaemon import utils from appdaemon import utils
from appdaemon.adbase import ADBase
from appdaemon.plugins.hass import Hass from appdaemon.plugins.hass import Hass
# from globals import GLOBAL_MODE, GLOBAL_VAR
class SimpleApp(Hass): class SimpleApp(Hass):
def initialize(self): def initialize(self) -> None:
match self.ping(): match self.ping():
case float() as ping: case float() as ping:
ping = utils.format_timedelta(ping) ping = utils.format_timedelta(ping)
@@ -20,81 +12,10 @@ class SimpleApp(Hass):
case _: case _:
pass pass
# self.get_entity('input_button.test_button').listen_state(self.handle_button)
# self.set_app_pin(True) class BaseApp(ADBase):
def initialize(self) -> None:
# Listen for a button press event with a specific entity_id
self.listen_event(
self.handle_button,
'call_service',
service='press',
entity_id='input_button.test_button'
)
def handle_button(self, event_type: str, data: dict[str, Any], **kwargs: Any) -> None:
match data:
case {"service_data": {"entity_id": eid}}:
friendly_name = self.get_state(eid, attribute='friendly_name')
self.log(f'pressed {friendly_name}')
case _:
self.log(f'Unhandled button press: {data}', level='WARNING')
def delayed(self, **kwargs):
self.log(f'This is a delayed log message: {kwargs}')
# self.reload_apps()
# self.stop_app('child_app')
def my_callback(self, event_type: str, data: dict[str, Any], **kwargs: Any) -> None:
match data:
case {
"service_data": {"entity_id": eid},
"metadata": {"time_fired": time_fired}
}:
friendly_name = self.get_state(eid, attribute='friendly_name')
time_fired = datetime.fromisoformat(time_fired).astimezone(self.AD.tz)
fmt = "%I:%M:%S %p"
self.log(f'{friendly_name} was pressed at {time_fired.strftime(fmt)}')
self.log(f'Kwargs: {kwargs}')
case _:
self.log(f'Unhandled button press: {data}', level='WARNING')
@ad.global_lock
def write(self):
return
# def test_event_handler(self, event_type: str, data: dict[str, Any], **kwargs: Any) -> None:
def test_event_handler(self, *_, value_at_listen, **kwargs: Any) -> None:
# self.log(f' {event_type} '.center(30, '#'))
self.log(f'Data from event: {value_at_listen}')
# self.log(f'Data from registration: {kwargs}')
return
class BaseApp(ad.ADBase):
def initialize(self):
self.adapi = self.get_ad_api() self.adapi = self.get_ad_api()
self.log = self.adapi.log self.log = self.adapi.log
self.hassapi = self.get_plugin_api("HASS") self.hassapi = self.get_plugin_api('HASS')
assert isinstance(self.hassapi, Hass) assert isinstance(self.hassapi, Hass), 'HASS API not available'
self.log(f'{self.__class__.__name__} Initialized')
self.config_model = {'name': self.__class__.__name__, 'module': 'simple_app', 'class': 'SimpleApp'}
self.global_vars['abc'] = 123
self.config
self.app_config
# self.log(f'Global mode is set to: {GLOBAL_MODE} ({GLOBAL_MODE.value})')
self.ad = self.AD
# self.adapi.run_in(self.delayed, 0.75)
# self.alexa_notify("Home assistant has been restarted", target="fake")
self.adapi.listen_event(
self.handle_button,
'call_service',
service='press',
entity_id='input_button.test_button'
)
def handle_button(self, event_type: str, data: dict[str, Any], **kwargs: Any) -> None:
self.adapi.fire_event("test_event", values_at_fire=123)

View File

@@ -1,14 +0,0 @@
#
# Main arguments, all optional
#
title: Hello Panel
widget_dimensions: [120, 120]
widget_margins: [5, 5]
columns: 8
label:
widget_type: label
text: Hello World
layout:
- label(2x2)

29
conf/pyrightconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
// Specify which paths to check
"include": [
"./ad-test/conf/apps/**"
],
// "typeCheckingMode": "standard",
"typeCheckingMode": "basic",
// Be somewhat tolerant with regards to imperfect dependencies or types
"reportUnknownMemberType": "none",
"reportUnusedImport": "warning",
"reportMissingTypeStubs": "none",
"reportIncompleteStub": "none",
"reportUnknownVariableType": "none",
"reportUnknownParameterType": "warning",
"reportMissingTypeArgument": "warning",
"useLibraryCodeForTypes": true, // Homogeneous with pylance's default
// Add warnings for probable programming errors
"reportUnnecessaryTypeIgnoreComment": "warning",
"reportMissingParameterType": "warning",
"reportUnnecessaryComparison": "warning",
"reportUnnecessaryIsInstance": "warning",
"reportUnnecessaryCast": "warning",
"reportUnnecessaryContains": "warning",
"reportMatchNotExhaustive": "error",
"reportUnusedVariable": "warning",
"reportUnusedCoroutine": "warning",
"reportUnusedExpression": "warning",
"reportUnusedFunction": "warning"
}

View File

@@ -8,18 +8,15 @@ authors = [
dependencies = [ dependencies = [
"pytest>=8.3.2", "pytest>=8.3.2",
"gitpython>=3.1.43", "gitpython>=3.1.43",
"appdaemon",
] ]
readme = "README.md" readme = "README.md"
requires-python = ">= 3.8" requires-python = ">= 3.10"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.rye]
managed = true
dev-dependencies = []
[tool.ruff.format] [tool.ruff.format]
quote-style = "single" quote-style = "single"
@@ -28,3 +25,11 @@ allow-direct-references = true
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src/ad_test"] packages = ["src/ad_test"]
[tool.uv.sources]
appdaemon = {path = "/home/john/Documents/appdaemon" }
[dependency-groups]
dev = [
"ruff>=0.11.13",
]

29
ruff.toml Normal file
View File

@@ -0,0 +1,29 @@
line-length = 100
target-version = "py312"
[format]
quote-style = "single"
indent-style = "space"
[lint]
exclude = [
"conf/apps/simple_app/malformed.py"
]
select = ["ALL"]
extend-ignore = [
"ANN",
"BLE001",
"COM812",
"D",
"E501",
"ERA001",
"INP001",
"S101",
]
[lint.flake8-quotes]
inline-quotes = "single"
docstring-quotes = "double"
multiline-quotes = "double"

View File

@@ -1,2 +0,0 @@
def hello() -> str:
return 'Hello from ad-test!'