wilhelmina 0.1.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.
- wilhelmina-0.1.0/LICENSE +21 -0
- wilhelmina-0.1.0/PKG-INFO +152 -0
- wilhelmina-0.1.0/README.md +131 -0
- wilhelmina-0.1.0/pyproject.toml +61 -0
- wilhelmina-0.1.0/wilhelmina/__init__.py +65 -0
- wilhelmina-0.1.0/wilhelmina/client.py +325 -0
- wilhelmina-0.1.0/wilhelmina/models/__init__.py +5 -0
- wilhelmina-0.1.0/wilhelmina/models/messages.py +75 -0
- wilhelmina-0.1.0/wilhelmina/tests/__init__.py +1 -0
- wilhelmina-0.1.0/wilhelmina/tests/conftest.py +130 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_client.py +706 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_error_handling.py +103 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_init.py +67 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_models.py +138 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_unread_messages.py +207 -0
- wilhelmina-0.1.0/wilhelmina/tests/test_utils.py +26 -0
- wilhelmina-0.1.0/wilhelmina/utils/__init__.py +23 -0
wilhelmina-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 {{cookiecutter.github_user}}
|
|
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.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wilhelmina
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: API client for Wilma school portal
|
|
5
|
+
Home-page: https://github.com/frwickst/wilma
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Frank Wickström
|
|
8
|
+
Author-email: frwickst@gmail.com
|
|
9
|
+
Requires-Python: >=3.13,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Provides-Extra: playwright
|
|
13
|
+
Requires-Dist: aiohttp (>=3.11.16,<4.0.0)
|
|
14
|
+
Requires-Dist: beautifulsoup4 (>=4.13.4,<5.0.0)
|
|
15
|
+
Requires-Dist: html2text (>=2025.4.15,<2026.0.0)
|
|
16
|
+
Requires-Dist: mashumaro (>=3.15,<4.0)
|
|
17
|
+
Requires-Dist: playwright (>=1.51.0,<2.0.0) ; extra == "playwright"
|
|
18
|
+
Project-URL: Repository, https://github.com/frwickst/wilma
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Wilhelmina - Wilma API Client
|
|
22
|
+
|
|
23
|
+
A Python API client for the Wilma school platform. This client allows you to authenticate, fetch messages, and view message contents from the Wilma platform.
|
|
24
|
+
|
|
25
|
+
Why is this not called Wilma? The reason is simple, PyPi sees "Wilma" as a prohibited packages name. It
|
|
26
|
+
is not taken on PyPi, it just is not allowed. Wilhelmina is the long/full version of the name Wilma which
|
|
27
|
+
is the reason for the name.
|
|
28
|
+
|
|
29
|
+
## Wilma API Features
|
|
30
|
+
|
|
31
|
+
- [X] Messages
|
|
32
|
+
- [X] List (API)
|
|
33
|
+
- [X] Get single (API)
|
|
34
|
+
- [X] Get unread (Scraping w/ Playwright)\*
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
\* There _is_ an API that returns the `status` of a message (0=read, 1=unread) but this is not enabled on all instances, specifically not on the Turku instance that I was interested in.
|
|
38
|
+
|
|
39
|
+
## Home Assistant compatibility
|
|
40
|
+
|
|
41
|
+
This library has support for passing in an existing `aiohttp` to allow for easier integration with
|
|
42
|
+
Home Assistant. The version of `aiohttp` has also been slightly restricted in order to be installable
|
|
43
|
+
in Home Assistant. `mashumaro` was chosen for type safe models instead of Pydantic to not need to
|
|
44
|
+
go through the v1 vs. v2 hassle.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- Asynchronous API using `aiohttp`
|
|
49
|
+
- Type-safe models using `mashumaro`
|
|
50
|
+
- Optional unread message detection using Playwright for JavaScript rendering
|
|
51
|
+
- Graceful fallback when Playwright is not installed (messages won't be marked as unread)
|
|
52
|
+
- Comprehensive test suite
|
|
53
|
+
|
|
54
|
+
## Note about AI
|
|
55
|
+
|
|
56
|
+
Sonnet 3.7 has written a lot code in this repository. Far from one-shotted however and a lot of
|
|
57
|
+
refactoring has been done in order to get things into this state. The main purpose for creating
|
|
58
|
+
this library was to get quick access to the Wilma API through Python.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Basic installation
|
|
64
|
+
pip install wilma
|
|
65
|
+
|
|
66
|
+
# (Optional) Installation with Playwright support (for unread message detection)
|
|
67
|
+
pip install wilma[playwright]
|
|
68
|
+
|
|
69
|
+
# Playwright browser will be installed automatically on import
|
|
70
|
+
|
|
71
|
+
# Using Poetry
|
|
72
|
+
poetry add wilma -E playwright
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Client Usage
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import asyncio
|
|
79
|
+
from wilma import WilmaClient
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
# Initialize client with an optional existing aiohttp session
|
|
83
|
+
# Use headless=False to see the browser
|
|
84
|
+
session = aiohttp.ClientSession() # Optional - provide your own session
|
|
85
|
+
|
|
86
|
+
async with WilmaClient("https://turku.inschool.fi", session=session, headless=True) as client:
|
|
87
|
+
# Login
|
|
88
|
+
await client.login("username", "password")
|
|
89
|
+
|
|
90
|
+
# Get messages
|
|
91
|
+
messages = await client.get_messages()
|
|
92
|
+
|
|
93
|
+
# Print messages
|
|
94
|
+
for msg in messages:
|
|
95
|
+
# Show unread status
|
|
96
|
+
unread_status = "UNREAD" if msg.unread else "read"
|
|
97
|
+
print(f"{msg.id}: {msg.subject} - {msg.timestamp} - {msg.sender} - {unread_status}")
|
|
98
|
+
|
|
99
|
+
# Get message content
|
|
100
|
+
content = await client.get_message_content(msg.id)
|
|
101
|
+
print(f"Content: {content.content[:100]}...")
|
|
102
|
+
|
|
103
|
+
# Run the main function
|
|
104
|
+
asyncio.run(main())
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
### Setup
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Clone the repository
|
|
113
|
+
git clone https://github.com/yourusername/wilma.git
|
|
114
|
+
cd wilma
|
|
115
|
+
|
|
116
|
+
# Install development dependencies
|
|
117
|
+
poetry install
|
|
118
|
+
|
|
119
|
+
# Install Playwright browser
|
|
120
|
+
poetry run playwright install chromium
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Running Tests
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Run all tests
|
|
127
|
+
poetry run pytest
|
|
128
|
+
|
|
129
|
+
# Run specific tests
|
|
130
|
+
poetry run pytest wilma/tests/test_client.py
|
|
131
|
+
|
|
132
|
+
# Run with coverage
|
|
133
|
+
poetry run pytest --cov=wilma
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Linting and Type Checking
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Format with ruff
|
|
140
|
+
poetry run ruff format wilma
|
|
141
|
+
|
|
142
|
+
# Run ruff for linting
|
|
143
|
+
poetry run ruff check wilma --fix
|
|
144
|
+
|
|
145
|
+
# Run mypy for type checking
|
|
146
|
+
poetry run mypy wilma
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
152
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Wilhelmina - Wilma API Client
|
|
2
|
+
|
|
3
|
+
A Python API client for the Wilma school platform. This client allows you to authenticate, fetch messages, and view message contents from the Wilma platform.
|
|
4
|
+
|
|
5
|
+
Why is this not called Wilma? The reason is simple, PyPi sees "Wilma" as a prohibited packages name. It
|
|
6
|
+
is not taken on PyPi, it just is not allowed. Wilhelmina is the long/full version of the name Wilma which
|
|
7
|
+
is the reason for the name.
|
|
8
|
+
|
|
9
|
+
## Wilma API Features
|
|
10
|
+
|
|
11
|
+
- [X] Messages
|
|
12
|
+
- [X] List (API)
|
|
13
|
+
- [X] Get single (API)
|
|
14
|
+
- [X] Get unread (Scraping w/ Playwright)\*
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
\* There _is_ an API that returns the `status` of a message (0=read, 1=unread) but this is not enabled on all instances, specifically not on the Turku instance that I was interested in.
|
|
18
|
+
|
|
19
|
+
## Home Assistant compatibility
|
|
20
|
+
|
|
21
|
+
This library has support for passing in an existing `aiohttp` to allow for easier integration with
|
|
22
|
+
Home Assistant. The version of `aiohttp` has also been slightly restricted in order to be installable
|
|
23
|
+
in Home Assistant. `mashumaro` was chosen for type safe models instead of Pydantic to not need to
|
|
24
|
+
go through the v1 vs. v2 hassle.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Asynchronous API using `aiohttp`
|
|
29
|
+
- Type-safe models using `mashumaro`
|
|
30
|
+
- Optional unread message detection using Playwright for JavaScript rendering
|
|
31
|
+
- Graceful fallback when Playwright is not installed (messages won't be marked as unread)
|
|
32
|
+
- Comprehensive test suite
|
|
33
|
+
|
|
34
|
+
## Note about AI
|
|
35
|
+
|
|
36
|
+
Sonnet 3.7 has written a lot code in this repository. Far from one-shotted however and a lot of
|
|
37
|
+
refactoring has been done in order to get things into this state. The main purpose for creating
|
|
38
|
+
this library was to get quick access to the Wilma API through Python.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Basic installation
|
|
44
|
+
pip install wilma
|
|
45
|
+
|
|
46
|
+
# (Optional) Installation with Playwright support (for unread message detection)
|
|
47
|
+
pip install wilma[playwright]
|
|
48
|
+
|
|
49
|
+
# Playwright browser will be installed automatically on import
|
|
50
|
+
|
|
51
|
+
# Using Poetry
|
|
52
|
+
poetry add wilma -E playwright
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API Client Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
from wilma import WilmaClient
|
|
60
|
+
|
|
61
|
+
async def main():
|
|
62
|
+
# Initialize client with an optional existing aiohttp session
|
|
63
|
+
# Use headless=False to see the browser
|
|
64
|
+
session = aiohttp.ClientSession() # Optional - provide your own session
|
|
65
|
+
|
|
66
|
+
async with WilmaClient("https://turku.inschool.fi", session=session, headless=True) as client:
|
|
67
|
+
# Login
|
|
68
|
+
await client.login("username", "password")
|
|
69
|
+
|
|
70
|
+
# Get messages
|
|
71
|
+
messages = await client.get_messages()
|
|
72
|
+
|
|
73
|
+
# Print messages
|
|
74
|
+
for msg in messages:
|
|
75
|
+
# Show unread status
|
|
76
|
+
unread_status = "UNREAD" if msg.unread else "read"
|
|
77
|
+
print(f"{msg.id}: {msg.subject} - {msg.timestamp} - {msg.sender} - {unread_status}")
|
|
78
|
+
|
|
79
|
+
# Get message content
|
|
80
|
+
content = await client.get_message_content(msg.id)
|
|
81
|
+
print(f"Content: {content.content[:100]}...")
|
|
82
|
+
|
|
83
|
+
# Run the main function
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
### Setup
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Clone the repository
|
|
93
|
+
git clone https://github.com/yourusername/wilma.git
|
|
94
|
+
cd wilma
|
|
95
|
+
|
|
96
|
+
# Install development dependencies
|
|
97
|
+
poetry install
|
|
98
|
+
|
|
99
|
+
# Install Playwright browser
|
|
100
|
+
poetry run playwright install chromium
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Running Tests
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Run all tests
|
|
107
|
+
poetry run pytest
|
|
108
|
+
|
|
109
|
+
# Run specific tests
|
|
110
|
+
poetry run pytest wilma/tests/test_client.py
|
|
111
|
+
|
|
112
|
+
# Run with coverage
|
|
113
|
+
poetry run pytest --cov=wilma
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Linting and Type Checking
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Format with ruff
|
|
120
|
+
poetry run ruff format wilma
|
|
121
|
+
|
|
122
|
+
# Run ruff for linting
|
|
123
|
+
poetry run ruff check wilma --fix
|
|
124
|
+
|
|
125
|
+
# Run mypy for type checking
|
|
126
|
+
poetry run mypy wilma
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "wilhelmina"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "API client for Wilma school portal"
|
|
5
|
+
authors = ["Frank Wickström <frwickst@gmail.com>"]
|
|
6
|
+
repository = "https://github.com/frwickst/wilma"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.13"
|
|
12
|
+
aiohttp = "^3.11.16"
|
|
13
|
+
mashumaro = "^3.15"
|
|
14
|
+
html2text = "^2025.4.15"
|
|
15
|
+
beautifulsoup4 = "^4.13.4"
|
|
16
|
+
playwright = {version = "^1.51.0", optional = true}
|
|
17
|
+
|
|
18
|
+
[tool.poetry.extras]
|
|
19
|
+
playwright = ["playwright"]
|
|
20
|
+
|
|
21
|
+
[tool.poetry.scripts]
|
|
22
|
+
wilma-install-deps = "wilma:install_playwright_deps"
|
|
23
|
+
|
|
24
|
+
[tool.poetry.group.dev.dependencies]
|
|
25
|
+
pytest = "^8.3.5"
|
|
26
|
+
pytest-asyncio = "^0.26.0"
|
|
27
|
+
pytest-cov = "^6.1.1"
|
|
28
|
+
mypy = "^1.15.0"
|
|
29
|
+
ruff = "^0.11.6"
|
|
30
|
+
types-beautifulsoup4 = "^4.12.0.20250204"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["poetry-core"]
|
|
34
|
+
build-backend = "poetry.core.masonry.api"
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 100
|
|
38
|
+
target-version = "py313"
|
|
39
|
+
src = ["wilma"]
|
|
40
|
+
lint.select = ["E", "F", "I"]
|
|
41
|
+
|
|
42
|
+
[tool.mypy]
|
|
43
|
+
python_version = "3.13"
|
|
44
|
+
strict = true
|
|
45
|
+
warn_unused_ignores = true
|
|
46
|
+
warn_return_any = true
|
|
47
|
+
warn_unreachable = true
|
|
48
|
+
disallow_any_generics = true
|
|
49
|
+
disallow_untyped_defs = true
|
|
50
|
+
check_untyped_defs = true
|
|
51
|
+
|
|
52
|
+
[[tool.mypy.overrides]]
|
|
53
|
+
module = "wilma.tests.*"
|
|
54
|
+
ignore_errors = true
|
|
55
|
+
|
|
56
|
+
[[tool.mypy.overrides]]
|
|
57
|
+
module = "playwright.*"
|
|
58
|
+
ignore_missing_imports = true
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Wilma API client."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from wilma.client import AuthenticationError, WilmaClient, WilmaError
|
|
9
|
+
from wilma.models import Message, MessagesList, Sender
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"WilmaClient",
|
|
15
|
+
"WilmaError",
|
|
16
|
+
"AuthenticationError",
|
|
17
|
+
"Message",
|
|
18
|
+
"MessagesList",
|
|
19
|
+
"Sender",
|
|
20
|
+
"install_playwright_deps",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def install_playwright_deps(force: bool = False, quiet: bool = False) -> bool:
|
|
25
|
+
"""Install Playwright dependencies.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
force: Force installation even if playwright is not installed
|
|
29
|
+
quiet: Don't log anything
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if installed, False if skipped or failed
|
|
33
|
+
"""
|
|
34
|
+
if not force:
|
|
35
|
+
# Check if playwright is installed
|
|
36
|
+
if importlib.util.find_spec("playwright") is None:
|
|
37
|
+
if not quiet:
|
|
38
|
+
logger.warning(
|
|
39
|
+
"Playwright is not installed. Install with 'pip install wilma[playwright]'"
|
|
40
|
+
)
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
log_level = logging.ERROR if quiet else logging.INFO
|
|
44
|
+
logger.setLevel(log_level)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
logger.info("Installing Playwright dependencies...")
|
|
48
|
+
subprocess.run(
|
|
49
|
+
[sys.executable, "-m", "playwright", "install", "chromium"],
|
|
50
|
+
check=True,
|
|
51
|
+
capture_output=quiet,
|
|
52
|
+
)
|
|
53
|
+
logger.info("Playwright dependencies installed successfully.")
|
|
54
|
+
return True
|
|
55
|
+
except (subprocess.SubprocessError, FileNotFoundError) as e:
|
|
56
|
+
logger.error(f"Failed to install Playwright dependencies: {e}")
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Auto-install Playwright browser when package is installed with the extra
|
|
61
|
+
if importlib.util.find_spec("playwright") is not None:
|
|
62
|
+
# Only attempt installation during import if playwright is available
|
|
63
|
+
# This ensures the browser is installed automatically when the package
|
|
64
|
+
# is installed with the playwright extra
|
|
65
|
+
install_playwright_deps(quiet=True)
|