pyedstem 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.
@@ -0,0 +1,244 @@
1
+
2
+ # General
3
+ .DS_Store
4
+ __MACOSX/
5
+ .AppleDouble
6
+ .LSOverride
7
+ Icon[
8
+ ]
9
+
10
+ # Thumbnails
11
+ ._*
12
+
13
+ # Files that might appear in the root of a volume
14
+ .DocumentRevisions-V100
15
+ .fseventsd
16
+ .Spotlight-V100
17
+ .TemporaryItems
18
+ .Trashes
19
+ .VolumeIcon.icns
20
+ .com.apple.timemachine.donotpresent
21
+
22
+ # Directories potentially created on remote AFP share
23
+ .AppleDB
24
+ .AppleDesktop
25
+ Network Trash Folder
26
+ Temporary Items
27
+ .apdisk
28
+
29
+ # Byte-compiled / optimized / DLL files
30
+ __pycache__/
31
+ *.py[codz]
32
+ *$py.class
33
+
34
+ # C extensions
35
+ *.so
36
+
37
+ # Distribution / packaging
38
+ .Python
39
+ build/
40
+ develop-eggs/
41
+ dist/
42
+ downloads/
43
+ eggs/
44
+ .eggs/
45
+ lib/
46
+ lib64/
47
+ parts/
48
+ sdist/
49
+ var/
50
+ wheels/
51
+ share/python-wheels/
52
+ *.egg-info/
53
+ .installed.cfg
54
+ *.egg
55
+ MANIFEST
56
+
57
+ # PyInstaller
58
+ # Usually these files are written by a python script from a template
59
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
60
+ *.manifest
61
+ *.spec
62
+
63
+ # Installer logs
64
+ pip-log.txt
65
+ pip-delete-this-directory.txt
66
+
67
+ # Unit test / coverage reports
68
+ htmlcov/
69
+ .tox/
70
+ .nox/
71
+ .coverage
72
+ .coverage.*
73
+ .cache
74
+ nosetests.xml
75
+ coverage.xml
76
+ *.cover
77
+ *.py.cover
78
+ .hypothesis/
79
+ .pytest_cache/
80
+ cover/
81
+
82
+ # Translations
83
+ *.mo
84
+ *.pot
85
+
86
+ # Django stuff:
87
+ *.log
88
+ local_settings.py
89
+ db.sqlite3
90
+ db.sqlite3-journal
91
+
92
+ # Flask stuff:
93
+ instance/
94
+ .webassets-cache
95
+
96
+ # Scrapy stuff:
97
+ .scrapy
98
+
99
+ # Sphinx documentation
100
+ docs/_build/
101
+
102
+ # PyBuilder
103
+ .pybuilder/
104
+ target/
105
+
106
+ # Jupyter Notebook
107
+ .ipynb_checkpoints
108
+
109
+ # IPython
110
+ profile_default/
111
+ ipython_config.py
112
+
113
+ # pyenv
114
+ # For a library or package, you might want to ignore these files since the code is
115
+ # intended to run in multiple environments; otherwise, check them in:
116
+ # .python-version
117
+
118
+ # pipenv
119
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
120
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
121
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
122
+ # install all needed dependencies.
123
+ # Pipfile.lock
124
+
125
+ # UV
126
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
127
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
128
+ # commonly ignored for libraries.
129
+ # uv.lock
130
+
131
+ # poetry
132
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
133
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
134
+ # commonly ignored for libraries.
135
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
136
+ # poetry.lock
137
+ # poetry.toml
138
+
139
+ # pdm
140
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
141
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
142
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
143
+ # pdm.lock
144
+ # pdm.toml
145
+ .pdm-python
146
+ .pdm-build/
147
+
148
+ # pixi
149
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
150
+ # pixi.lock
151
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
152
+ # in the .venv directory. It is recommended not to include this directory in version control.
153
+ .pixi
154
+
155
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
156
+ __pypackages__/
157
+
158
+ # Celery stuff
159
+ celerybeat-schedule
160
+ celerybeat.pid
161
+
162
+ # Redis
163
+ *.rdb
164
+ *.aof
165
+ *.pid
166
+
167
+ # RabbitMQ
168
+ mnesia/
169
+ rabbitmq/
170
+ rabbitmq-data/
171
+
172
+ # ActiveMQ
173
+ activemq-data/
174
+
175
+ # SageMath parsed files
176
+ *.sage.py
177
+
178
+ # Environments
179
+ .env
180
+ .envrc
181
+ .venv
182
+ env/
183
+ venv/
184
+ ENV/
185
+ env.bak/
186
+ venv.bak/
187
+
188
+ # Spyder project settings
189
+ .spyderproject
190
+ .spyproject
191
+
192
+ # Rope project settings
193
+ .ropeproject
194
+
195
+ # mkdocs documentation
196
+ /site
197
+
198
+ # mypy
199
+ .mypy_cache/
200
+ .dmypy.json
201
+ dmypy.json
202
+
203
+ # Pyre type checker
204
+ .pyre/
205
+
206
+ # pytype static type analyzer
207
+ .pytype/
208
+
209
+ # Cython debug symbols
210
+ cython_debug/
211
+
212
+ # PyCharm
213
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
214
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
215
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
216
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
217
+ # .idea/
218
+
219
+ # Abstra
220
+ # Abstra is an AI-powered process automation framework.
221
+ # Ignore directories containing user credentials, local state, and settings.
222
+ # Learn more at https://abstra.io/docs
223
+ .abstra/
224
+
225
+ # Visual Studio Code
226
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
227
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
228
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
229
+ # you could uncomment the following to ignore the entire vscode folder
230
+ # .vscode/
231
+
232
+ # Ruff stuff:
233
+ .ruff_cache/
234
+
235
+ # PyPI configuration file
236
+ .pypirc
237
+
238
+ # Marimo
239
+ marimo/_static/
240
+ marimo/_lsp/
241
+ __marimo__/
242
+
243
+ # Streamlit
244
+ .streamlit/secrets.toml
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyedstem
3
+ Version: 0.1.0
4
+ Summary: Easy-to-use Python client for the Ed Stem API
5
+ Project-URL: Homepage, https://github.com/AlexDrBanana/pyedstem
6
+ Project-URL: Repository, https://github.com/AlexDrBanana/pyedstem
7
+ Project-URL: Issues, https://github.com/AlexDrBanana/pyedstem/issues
8
+ Author: AlexDrBanana
9
+ Keywords: api-client,ed-stem,edstem,education,python
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Classifier: Topic :: Education
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: httpx>=0.28.1
22
+ Requires-Dist: markdownify>=1.2.0
23
+ Requires-Dist: pydantic-settings>=2.8.1
24
+ Requires-Dist: pydantic>=2.11.3
25
+ Description-Content-Type: text/markdown
26
+
27
+ # pyedstem
28
+
29
+ Typed Python client for the Ed Stem API.
30
+
31
+ `pyedstem` provides a small, sync-first interface for common Ed Stem tasks such
32
+ as listing active courses, retrieving discussion threads, reading lessons, and
33
+ posting answers back to Ed using the XML document format the API expects.
34
+
35
+ ## Installation
36
+
37
+ ### Install with pip
38
+
39
+ ```bash
40
+ pip install pyedstem
41
+ ```
42
+
43
+ ### Add to a uv project
44
+
45
+ ```bash
46
+ uv add pyedstem
47
+ ```
48
+
49
+ ## Configuration
50
+
51
+ You can create a client directly with an API token:
52
+
53
+ ```python
54
+ from pyedstem import EdStemClient
55
+
56
+ client = EdStemClient(api_token="your-edstem-token")
57
+ ```
58
+
59
+ Or load configuration from environment variables:
60
+
61
+ - `EDSTEM_API_TOKEN` — required
62
+ - `EDSTEM_BASE_URL` — optional, defaults to `https://edstem.org/api`
63
+ - `EDSTEM_TIMEOUT_SECONDS` — optional, defaults to `30.0`
64
+
65
+ ```python
66
+ from pyedstem import EdStemClient
67
+
68
+ with EdStemClient.from_env() as client:
69
+ current_user = client.user.get_current_user()
70
+ ```
71
+
72
+ ## Quick start
73
+
74
+ ### List active courses
75
+
76
+ ```python
77
+ from pyedstem import EdStemClient
78
+
79
+ with EdStemClient.from_env() as client:
80
+ active_courses = client.courses.list_active()
81
+
82
+ for course in active_courses:
83
+ print(course.id, course.code, course.name)
84
+ ```
85
+
86
+ ### Fetch unanswered discussion threads
87
+
88
+ ```python
89
+ from pyedstem import EdStemClient
90
+
91
+ course_id = 12345
92
+
93
+ with EdStemClient.from_env() as client:
94
+ threads = client.workflows.list_course_unanswered_threads(course_id)
95
+
96
+ for thread in threads:
97
+ print(f"#{thread.number}: {thread.title}")
98
+ ```
99
+
100
+ ### Post an answer
101
+
102
+ ```python
103
+ from pyedstem import EdStemClient
104
+
105
+ with EdStemClient.from_env() as client:
106
+ client.threads.post_answer(
107
+ thread_id=67890,
108
+ markdown="Hi there,\n\nThanks for the question...",
109
+ )
110
+ ```
111
+
112
+ ## Features
113
+
114
+ - typed models for common Ed Stem responses
115
+ - sync-first client API with resource groups
116
+ - workflow helpers for active-course and unanswered-thread automation
117
+ - markdown-to-Ed document conversion for answer posting
118
+ - opt-in live contract tests for undocumented API drift detection
119
+
120
+ ## Development
121
+
122
+ Clone the repository, then install development dependencies with uv:
123
+
124
+ ```bash
125
+ uv sync --group dev
126
+ ```
127
+
128
+ Run the test suite:
129
+
130
+ ```bash
131
+ uv run pytest tests
132
+ ```
133
+
134
+ ### Live contract tests
135
+
136
+ The repository includes an opt-in live suite under `tests/live/` that probes
137
+ documented Ed endpoints so API changes can be detected early.
138
+
139
+ Safe read-only live tests:
140
+
141
+ ```bash
142
+ EDSTEM_RUN_LIVE_TESTS=1 uv run pytest tests/live/test_endpoint_contracts.py tests/live/test_restricted_endpoint_contracts.py
143
+ ```
144
+
145
+ Opt-in write test for posting answers:
146
+
147
+ ```bash
148
+ EDSTEM_RUN_WRITE_TESTS=1 \
149
+ EDSTEM_WRITE_TEST_THREAD_ID=12345 \
150
+ uv run pytest tests/live/test_write_endpoint_contracts.py
151
+ ```
152
+
153
+ Only enable the write suite when it is safe to mutate real Ed data.
154
+
155
+ ## Publishing
156
+
157
+ Build distribution artifacts with:
158
+
159
+ ```bash
160
+ uv build
161
+ ```
162
+
163
+ Publish to PyPI with:
164
+
165
+ ```bash
166
+ uv publish
167
+ ```
168
+
169
+ `uv publish` expects PyPI credentials or trusted publishing to be configured in
170
+ the environment where you run the release.
@@ -0,0 +1,144 @@
1
+ # pyedstem
2
+
3
+ Typed Python client for the Ed Stem API.
4
+
5
+ `pyedstem` provides a small, sync-first interface for common Ed Stem tasks such
6
+ as listing active courses, retrieving discussion threads, reading lessons, and
7
+ posting answers back to Ed using the XML document format the API expects.
8
+
9
+ ## Installation
10
+
11
+ ### Install with pip
12
+
13
+ ```bash
14
+ pip install pyedstem
15
+ ```
16
+
17
+ ### Add to a uv project
18
+
19
+ ```bash
20
+ uv add pyedstem
21
+ ```
22
+
23
+ ## Configuration
24
+
25
+ You can create a client directly with an API token:
26
+
27
+ ```python
28
+ from pyedstem import EdStemClient
29
+
30
+ client = EdStemClient(api_token="your-edstem-token")
31
+ ```
32
+
33
+ Or load configuration from environment variables:
34
+
35
+ - `EDSTEM_API_TOKEN` — required
36
+ - `EDSTEM_BASE_URL` — optional, defaults to `https://edstem.org/api`
37
+ - `EDSTEM_TIMEOUT_SECONDS` — optional, defaults to `30.0`
38
+
39
+ ```python
40
+ from pyedstem import EdStemClient
41
+
42
+ with EdStemClient.from_env() as client:
43
+ current_user = client.user.get_current_user()
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ### List active courses
49
+
50
+ ```python
51
+ from pyedstem import EdStemClient
52
+
53
+ with EdStemClient.from_env() as client:
54
+ active_courses = client.courses.list_active()
55
+
56
+ for course in active_courses:
57
+ print(course.id, course.code, course.name)
58
+ ```
59
+
60
+ ### Fetch unanswered discussion threads
61
+
62
+ ```python
63
+ from pyedstem import EdStemClient
64
+
65
+ course_id = 12345
66
+
67
+ with EdStemClient.from_env() as client:
68
+ threads = client.workflows.list_course_unanswered_threads(course_id)
69
+
70
+ for thread in threads:
71
+ print(f"#{thread.number}: {thread.title}")
72
+ ```
73
+
74
+ ### Post an answer
75
+
76
+ ```python
77
+ from pyedstem import EdStemClient
78
+
79
+ with EdStemClient.from_env() as client:
80
+ client.threads.post_answer(
81
+ thread_id=67890,
82
+ markdown="Hi there,\n\nThanks for the question...",
83
+ )
84
+ ```
85
+
86
+ ## Features
87
+
88
+ - typed models for common Ed Stem responses
89
+ - sync-first client API with resource groups
90
+ - workflow helpers for active-course and unanswered-thread automation
91
+ - markdown-to-Ed document conversion for answer posting
92
+ - opt-in live contract tests for undocumented API drift detection
93
+
94
+ ## Development
95
+
96
+ Clone the repository, then install development dependencies with uv:
97
+
98
+ ```bash
99
+ uv sync --group dev
100
+ ```
101
+
102
+ Run the test suite:
103
+
104
+ ```bash
105
+ uv run pytest tests
106
+ ```
107
+
108
+ ### Live contract tests
109
+
110
+ The repository includes an opt-in live suite under `tests/live/` that probes
111
+ documented Ed endpoints so API changes can be detected early.
112
+
113
+ Safe read-only live tests:
114
+
115
+ ```bash
116
+ EDSTEM_RUN_LIVE_TESTS=1 uv run pytest tests/live/test_endpoint_contracts.py tests/live/test_restricted_endpoint_contracts.py
117
+ ```
118
+
119
+ Opt-in write test for posting answers:
120
+
121
+ ```bash
122
+ EDSTEM_RUN_WRITE_TESTS=1 \
123
+ EDSTEM_WRITE_TEST_THREAD_ID=12345 \
124
+ uv run pytest tests/live/test_write_endpoint_contracts.py
125
+ ```
126
+
127
+ Only enable the write suite when it is safe to mutate real Ed data.
128
+
129
+ ## Publishing
130
+
131
+ Build distribution artifacts with:
132
+
133
+ ```bash
134
+ uv build
135
+ ```
136
+
137
+ Publish to PyPI with:
138
+
139
+ ```bash
140
+ uv publish
141
+ ```
142
+
143
+ `uv publish` expects PyPI credentials or trusted publishing to be configured in
144
+ the environment where you run the release.
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "pyedstem"
3
+ version = "0.1.0"
4
+ description = "Easy-to-use Python client for the Ed Stem API"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ authors = [
8
+ { name = "AlexDrBanana" },
9
+ ]
10
+ keywords = ["edstem", "ed-stem", "education", "api-client", "python"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Intended Audience :: Education",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Programming Language :: Python :: Implementation :: CPython",
19
+ "Topic :: Education",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.28.1",
25
+ "markdownify>=1.2.0",
26
+ "pydantic>=2.11.3",
27
+ "pydantic-settings>=2.8.1",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/AlexDrBanana/pyedstem"
32
+ Repository = "https://github.com/AlexDrBanana/pyedstem"
33
+ Issues = "https://github.com/AlexDrBanana/pyedstem/issues"
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/pyedstem"]
41
+
42
+ [tool.hatch.build.targets.wheel.sources]
43
+ "src" = ""
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pytest>=8.4.2",
48
+ ]
49
+
50
+ [tool.pytest.ini_options]
51
+ pythonpath = ["src"]
@@ -0,0 +1,12 @@
1
+ """Public package exports for pyedstem."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from pyedstem.client import EdStemClient
6
+
7
+ try:
8
+ __version__ = version("pyedstem")
9
+ except PackageNotFoundError:
10
+ __version__ = "0.0.0"
11
+
12
+ __all__ = ["EdStemClient", "__version__"]
@@ -0,0 +1,74 @@
1
+ """Top-level client for the Ed Stem API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from pyedstem.config import get_settings
10
+ from pyedstem.resources.analytics import AnalyticsResource
11
+ from pyedstem.resources.challenges import ChallengesResource
12
+ from pyedstem.resources.courses import CoursesResource
13
+ from pyedstem.resources.lessons import LessonsResource
14
+ from pyedstem.resources.threads import ThreadsResource
15
+ from pyedstem.resources.user import UserResource
16
+ from pyedstem.transport import EdStemTransport
17
+ from pyedstem.workflow_client import WorkflowClient
18
+
19
+
20
+ class EdStemClient:
21
+ """High-level sync client for the Ed Stem API."""
22
+
23
+ def __init__(
24
+ self,
25
+ *,
26
+ api_token: str,
27
+ base_url: str = "https://edstem.org/api",
28
+ timeout_seconds: float = 30.0,
29
+ http_client: httpx.Client | None = None,
30
+ ) -> None:
31
+ self._transport = EdStemTransport(
32
+ api_token=api_token,
33
+ base_url=base_url,
34
+ timeout_seconds=timeout_seconds,
35
+ client=http_client,
36
+ )
37
+ self.user = UserResource(self._transport)
38
+ self.courses = CoursesResource(self._transport)
39
+ self.threads = ThreadsResource(self._transport)
40
+ self.lessons = LessonsResource(self._transport)
41
+ self.analytics = AnalyticsResource(self._transport)
42
+ self.challenges = ChallengesResource(self._transport)
43
+ self.workflows = WorkflowClient(self)
44
+
45
+ @classmethod
46
+ def from_env(cls) -> "EdStemClient":
47
+ """Create a client from environment configuration."""
48
+ settings = get_settings()
49
+ return cls(
50
+ api_token=settings.api_token.get_secret_value(),
51
+ base_url=settings.base_url,
52
+ timeout_seconds=settings.timeout_seconds,
53
+ )
54
+
55
+ def close(self) -> None:
56
+ """Close the underlying HTTP transport."""
57
+ self._transport.close()
58
+
59
+ def get_json(
60
+ self,
61
+ path: str,
62
+ *,
63
+ params: dict[str, Any] | None = None,
64
+ ) -> dict[str, Any]:
65
+ """Perform a raw JSON GET request for less common endpoints."""
66
+ return self._transport.get_json(path, params=params)
67
+
68
+ def __enter__(self) -> "EdStemClient":
69
+ """Enter a context-managed client session."""
70
+ return self
71
+
72
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
73
+ """Always close HTTP resources when leaving a context manager."""
74
+ self.close()