Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
11a1fbd
Bump requests from 2.32.0 to 2.32.4
dependabot[bot] Jun 10, 2025
031f42d
Merge pull request #184 from metaodi/dependabot/pip/requests-2.32.4
metaodi Jun 10, 2025
8195fd9
Initial plan
Copilot Feb 15, 2026
b54fd81
Add type hints and mypy checking to osmapi
Copilot Feb 15, 2026
f9b2939
Add comprehensive type hints to OsmApi.py
Copilot Feb 15, 2026
bbc944f
Fix mypy type errors in osmapi package
Copilot Feb 15, 2026
d31826e
Complete type hints implementation with mypy integration
Copilot Feb 15, 2026
483ac12
Fix HTTP method signatures to accept Optional[bytes] for data parameter
Copilot Feb 15, 2026
db6de31
Remove unused return value from _assign_id_and_version method
Copilot Feb 15, 2026
d03ba4a
Update OsmApi.py to use Python 3.9+ built-in generic types
Copilot Feb 15, 2026
91b5307
Drop Python 3.8 support and modernize type hints to use built-in gene…
Copilot Feb 15, 2026
3a1c95a
Add noqa: C901 to ignore complexity warnings for complex methods
Copilot Feb 15, 2026
02b17d4
Initial plan
Claude Feb 17, 2026
5ef3a81
Remove autochangeset parameters and methods from OsmApi
Claude Feb 17, 2026
1902667
Regenerate documentation after removing autochangeset feature
Claude Feb 17, 2026
19913ae
Remove unused test fixture for autochangeset
Claude Feb 17, 2026
acfbe08
Change Python version for Black to 3.9
metaodi Feb 17, 2026
047ea08
Update CHANGELOG for feature removal and addition
metaodi Feb 17, 2026
18f3452
Update CHANGELOG for version 4.3.0 changes
metaodi Feb 17, 2026
116c746
Merge pull request #187 from metaodi/claude/remove-autochangeset-feature
metaodi Feb 17, 2026
c3a26d1
Merge branch 'develop' into copilot/add-type-hints-to-library
metaodi Feb 17, 2026
cec7383
Address code review feedback: improve type safety
Copilot Feb 17, 2026
55697b4
Widen HTTP layer to accept str | bytes for send parameter
Copilot Feb 17, 2026
4e3d9fc
Add missing return type annotations for close() and Changeset()
Copilot Feb 17, 2026
96a3c6a
Merge pull request #186 from metaodi/copilot/add-type-hints-to-library
metaodi Feb 17, 2026
69f2d55
Initial plan
Claude Feb 17, 2026
03130b2
Refactor to pythonic snake_case API with backward compatibility
Claude Feb 17, 2026
3043e6f
Delete osmapi/deprecated.py
metaodi Feb 17, 2026
0508d3c
Delete osmapi/OsmApi.py.backup
metaodi Feb 17, 2026
38a9b47
Fix bbox and time parameter formatting to remove spaces
Claude Feb 17, 2026
5f66e91
Pin black to 24.8.0 and fix formatting for compatibility
Claude Feb 17, 2026
8d9626d
Update Python version requirement to 3.9
metaodi Feb 17, 2026
84d322a
Add Python 3.12
metaodi Feb 18, 2026
49be5e7
Update examples to use the new syntax
metaodi Feb 18, 2026
84bb845
Update module docstrings
metaodi Feb 18, 2026
02023ba
Add params to http
metaodi Feb 18, 2026
909049b
Fix linting errors
metaodi Feb 18, 2026
c0cade3
Merge branch 'develop' into claude/refactor-to-pythonic-style
metaodi Feb 18, 2026
b0318e3
Update tests
metaodi Feb 18, 2026
6e05da8
Merge branch 'claude/refactor-to-pythonic-style' of https://github.co…
metaodi Feb 18, 2026
4ab2db6
Update test fixtures and deprecation tests
metaodi Feb 18, 2026
2b2ba93
Fix linting issues
metaodi Feb 18, 2026
5b1a4c3
Fix empty line
metaodi Feb 18, 2026
e6abbd6
Rename remaining fixture files to snake_case
Claude Feb 18, 2026
2ddcb30
Remove logger.exception to prevent too verbose output
metaodi Feb 18, 2026
664ad13
Rename more CamelCase parameters and methods to snake_case
metaodi Feb 18, 2026
877f6f4
More renamings
metaodi Feb 18, 2026
ee69407
Merge pull request #189 from metaodi/claude/refactor-to-pythonic-style
metaodi Feb 18, 2026
c0dd0de
Update docs
metaodi Feb 18, 2026
b3dc6fb
Release 5.0.0
metaodi Feb 18, 2026
537e01b
Update tests
metaodi Feb 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/publish_python.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# workflow inspired by chezou/tabula-py
name: Upload Python Package

on:
Expand All @@ -14,7 +13,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -46,7 +45,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ repos:
rev: 23.7.0
hooks:
- id: black
language_version: python3.8
language_version: python3.12
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
args: [--config-file=setup.cfg]

15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

## [5.0.0] - 2026-02-18
### Changed
- **BC-Break**: Remove support for Python 3.8, new minimum version for osmapi is Python 3.9
- **BC-Break**: Renamed all methods as `snake_case` instead of `CamelCase`(eg. `osmapi.node_get` instead of `osmapi.NodeGet`). The previous methods are still there, but all issue a `DeprecationWarning` when called.
- While changing the public API of osmapi, the large `OsmApi.py` file was split into several smaller files.

### Added
- Add type hints and mypy checking to osmapi #186

### Removed
- Remove autochangeset feature in favor of Changeset context manager #187

## [4.3.0] - 2025-01-21
### Added
- New `ConnectionApiError` when a connection or network error occurs (see issue #176, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
Expand Down Expand Up @@ -367,7 +379,8 @@ Miroslav Šedivý
- `Fixed` for any bug fixes.
- `Security` to invite users to upgrade in case of vulnerabilities.

[Unreleased]: https://github.com/metaodi/osmapi/compare/v4.3.0...HEAD
[Unreleased]: https://github.com/metaodi/osmapi/compare/v5.0.0...HEAD
[5.0.0]: https://github.com/metaodi/osmapi/compare/v4.3.0...v5.0.0
[4.3.0]: https://github.com/metaodi/osmapi/compare/v4.2.0...v4.3.0
[4.2.0]: https://github.com/metaodi/osmapi/compare/v4.1.0...v4.2.0
[4.1.0]: https://github.com/metaodi/osmapi/compare/v4.0.0...v4.1.0
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ format: ## Format source code (black codestyle)
lint: ## Linting of source code
python -m black --check --diff osmapi examples tests *.py
python -m flake8 --statistics --show-source .
python -m mypy osmapi

test: ## Run tests (run in UTF-8 mode in Windows)
python -Xutf8 -m pytest --cov=osmapi tests/
Expand Down
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ osmapi
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)


Python wrapper for the OSM API (requires Python >= 3.8)
Python wrapper for the OSM API (requires Python >= 3.9).

**NOTE**: Since version 5.0 of this library, all method names are in `snake_case`, the `CamelCase` versions are deprecated and will be removed in version 6.0.

## Installation

Expand All @@ -36,10 +38,11 @@ Check the [examples directory](https://github.com/metaodi/osmapi/tree/develop/ex

### Read from OpenStreetMap


```python
>>> import osmapi
>>> api = osmapi.OsmApi()
>>> print(api.NodeGet(123))
>>> print(api.node_get(123))
{'changeset': 532907, 'uid': 14298,
'timestamp': '2007-09-29T09:19:17Z',
'lon': 10.790009299999999, 'visible': True,
Expand All @@ -49,13 +52,14 @@ Check the [examples directory](https://github.com/metaodi/osmapi/tree/develop/ex

### Write to OpenStreetMap


```python
>>> import osmapi
>>> api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username = "metaodi", password = "*******")
>>> api.ChangesetCreate({"comment": "My first test"})
>>> print(api.NodeCreate({"lon":1, "lat":1, "tag": {}}))
>>> api.changeset_create({"comment": "My first test"})
>>> print(api.node_create({"lon":1, "lat":1, "tag": {}}))
{'changeset': 532907, 'lon': 1, 'version': 1, 'lat': 1, 'tag': {}, 'id': 164684}
>>> api.ChangesetClose()
>>> api.changeset_close()
```

### OAuth authentication
Expand All @@ -69,6 +73,8 @@ To use OAuth 2.0, you must register an application with an OpenStreetMap account
or on the [production server](https://www.openstreetmap.org/oauth2/applications).
Once this registration is done, you'll get a `client_id` and a `client_secret` that you can use to authenticate users.

auth = OpenStreetMapDevAuth(

Example code using [`cli-oauth2`](https://github.com/Zverik/cli-oauth2) on the development server, replace `OpenStreetMapDevAuth` with `OpenStreetMapAuth` to use the production server:

```python
Expand All @@ -87,9 +93,9 @@ api = osmapi.OsmApi(
session=auth.session
)

with api.Changeset({"comment": "My first test"}) as changeset_id:
with api.changeset({"comment": "My first test"}) as changeset_id:
print(f"Part of Changeset {changeset_id}")
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
print(node1)
```

Expand All @@ -103,6 +109,7 @@ To credit the application that supplies changes to OSM, an `appid` can be provid
This is a string identifying the application.
If this is omitted "osmapi" is used.


```python
api = osmapi.OsmApi(
api="https://api06.dev.openstreetmap.org",
Expand Down
231 changes: 3 additions & 228 deletions docs/index.html

Large diffs are not rendered by default.

132 changes: 77 additions & 55 deletions docs/osmapi.html

Large diffs are not rendered by default.

9,890 changes: 2,686 additions & 7,204 deletions docs/osmapi/OsmApi.html

Large diffs are not rendered by default.

437 changes: 437 additions & 0 deletions docs/osmapi/capabilities.html

Large diffs are not rendered by default.

1,744 changes: 1,744 additions & 0 deletions docs/osmapi/changeset.html

Large diffs are not rendered by default.

799 changes: 422 additions & 377 deletions docs/osmapi/dom.html

Large diffs are not rendered by default.

1,087 changes: 548 additions & 539 deletions docs/osmapi/errors.html

Large diffs are not rendered by default.

921 changes: 515 additions & 406 deletions docs/osmapi/http.html

Large diffs are not rendered by default.

1,488 changes: 1,488 additions & 0 deletions docs/osmapi/node.html

Large diffs are not rendered by default.

924 changes: 924 additions & 0 deletions docs/osmapi/note.html

Large diffs are not rendered by default.

605 changes: 313 additions & 292 deletions docs/osmapi/parser.html

Large diffs are not rendered by default.

1,083 changes: 1,083 additions & 0 deletions docs/osmapi/relation.html

Large diffs are not rendered by default.

1,471 changes: 1,471 additions & 0 deletions docs/osmapi/way.html

Large diffs are not rendered by default.

219 changes: 124 additions & 95 deletions docs/osmapi/xmlbuilder.html

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions docs/search.js

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions examples/changesets.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import osmapi
from pprint import pprint


api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")


try:
api.ChangesetGet(111111111111)
api.changeset_get(111111111111)
except osmapi.ApiError as e:
print(f"Error: {e}")
if e.status == 404:
print("Changeset not found")


print("")
pprint(api.ChangesetGet(12345))
pprint(api.changeset_get(12345))
21 changes: 10 additions & 11 deletions examples/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import sys
import urllib3


load_dotenv(find_dotenv())

# logging setup
Expand Down Expand Up @@ -43,7 +42,7 @@ def clear_screen():
log.debug("Try to write data to OSM without a changeset")
api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
try:
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
except osmapi.NoChangesetOpenError as e:
log.exception(e)
log.debug("There is no open changeset")
Expand All @@ -55,7 +54,7 @@ def clear_screen():
log.debug("Connect to wrong server...")
api = osmapi.OsmApi(api="https://invalid.server.name")
try:
api.ChangesetGet(123)
api.changeset_get(123)
except osmapi.ConnectionApiError as e:
log.exception(e)
log.debug("Error connecting to server")
Expand All @@ -67,7 +66,7 @@ def clear_screen():
log.debug("Request non-existent changeset id...")
api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
try:
api.ChangesetGet(111111111111)
api.changeset_get(111111111111)
except osmapi.ElementNotFoundApiError as e:
log.exception(e)
log.debug("Changeset not found")
Expand All @@ -81,8 +80,8 @@ def clear_screen():
s = requests.Session()
s.auth = ("user", "pass")
api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=s)
with api.Changeset({"comment": "My first test"}) as changeset_id:
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
with api.changeset({"comment": "My first test"}) as changeset_id:
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
except osmapi.UnauthorizedApiError as e:
log.exception(e)
log.debug("Unauthorized to make this request")
Expand All @@ -93,8 +92,8 @@ def clear_screen():
log.debug("Try to add data without authorization")
try:
api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
with api.Changeset({"comment": "My first test"}) as changeset_id:
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
with api.changeset({"comment": "My first test"}) as changeset_id:
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
except osmapi.UsernamePasswordMissingError as e:
log.exception(e)
log.debug("Username/Password or authorization missing")
Expand All @@ -117,13 +116,13 @@ def clear_screen():

try:
api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=auth.session)
with api.Changeset({"comment": "My first test"}) as changeset_id:
with api.changeset({"comment": "My first test"}) as changeset_id:
log.debug(f"Part of Changeset {changeset_id}")
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
log.debug(node1)

# get all the info from the closed changeset
changeset = api.ChangesetGet(changeset_id)
changeset = api.changeset_get(changeset_id)
log.debug(changeset)
exit_code = 0
except osmapi.ConnectionApiError as e:
Expand Down
2 changes: 1 addition & 1 deletion examples/log_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@


api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
node1 = api.NodeGet("1111")
node1 = api.node_get("1111")
log.debug(pformat(node1))
34 changes: 19 additions & 15 deletions examples/notes.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
import osmapi
from oauthcli import OpenStreetMapDevAuth
from dotenv import load_dotenv, find_dotenv
import os
from pprint import pprint

load_dotenv(find_dotenv())
user = os.getenv("OSM_USER")
pw = os.getenv("OSM_PASS")

api = osmapi.OsmApi(
api="https://api06.dev.openstreetmap.org", username=user, password=pw
)
empty_notes = api.NotesGet(
# load secrets for OAuth
client_id = os.getenv("OSM_OAUTH_CLIENT_ID")
client_secret = os.getenv("OSM_OAUTH_CLIENT_SECRET")

auth = OpenStreetMapDevAuth(
client_id, client_secret, ["write_api", "write_notes"]
).auth_code()

api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=auth.session)
empty_notes = api.notes_get(
-93.8472901, 35.9763601, -80, 36.176360100000004, limit=1, closed=0
)
pprint(empty_notes)


# create note and then search for it
note = api.NoteCreate(
note = api.note_create(
{
"lat": 47.3383501,
"lon": 8.5339522,
"text": "test note",
}
)
test_notes = api.NotesGet(8.527504, 47.337063, 8.540679, 47.341673, limit=1, closed=0)
test_notes = api.notes_get(8.527504, 47.337063, 8.540679, 47.341673, limit=1, closed=0)
pprint(test_notes)


api.NoteComment(note["id"], "Another comment")
api.NoteClose(note["id"], "Close this test note")

api.note_comment(note["id"], "Another comment")
api.note_close(note["id"], "Close this test note")

# try to close an already closed note
try:
api.NoteClose(note["id"], "Close the note again")
api.note_close(note["id"], "Close the note again")
except osmapi.NoteAlreadyClosedApiError:
print("")
print(f"The note {note['id']} has already been closed")


# try to comment on closed note
try:
api.NoteComment(note["id"], "Just a comment")
except osmapi.NoteAlreadyClosedApiError:
api.note_comment(note["id"], "Just a comment")
except (osmapi.NoteAlreadyClosedApiError, osmapi.errors.ApiError):
print("")
print(f"The note {note['id']} is closed, comment no longer possible")
5 changes: 3 additions & 2 deletions examples/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@
oauth_session.auth = auth

# use the custom session

api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=oauth_session)
with api.Changeset({"comment": "My first test"}) as changeset_id:
with api.changeset({"comment": "My first test"}) as changeset_id:
print(f"Part of Changeset {changeset_id}")
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
print(node1)
4 changes: 2 additions & 2 deletions examples/oauth2_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ def make_osm_change(oauth_session):
api = osmapi.OsmApi(
api="https://api06.dev.openstreetmap.org", session=oauth_session
)
with api.Changeset({"comment": "My first test"}) as changeset_id:
with api.changeset({"comment": "My first test"}) as changeset_id:
print(f"Part of Changeset {changeset_id}")
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
print(node1)


Expand Down
6 changes: 4 additions & 2 deletions examples/timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@


# Use a normal timeout (30s is the default value)

normal_timeout_api = osmapi.OsmApi(
api="https://api06.dev.openstreetmap.org", session=auth.session, timeout=30
)
changeset_id = normal_timeout_api.ChangesetCreate({"comment": "My first test"})
changeset_id = normal_timeout_api.changeset_create({"comment": "My first test"})
print(f"Create new changeset {changeset_id}")

# Deliberately using a very small timeout to show what happens when a timeout occurs

low_timeout_api = osmapi.OsmApi(
api="https://api06.dev.openstreetmap.org", session=auth.session, timeout=0.00001
)
try:
changeset_id = low_timeout_api.ChangesetCreate({"comment": "My first test"})
changeset_id = low_timeout_api.changeset_create({"comment": "My first test"})
print(f"Create new changeset {changeset_id}")
except osmapi.errors.TimeoutApiError as e:
print(f"Timeout error occured: {str(e)}")
8 changes: 4 additions & 4 deletions examples/write_to_osm.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@


api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=auth.session)
with api.Changeset({"comment": "My first test"}) as changeset_id:
with api.changeset({"comment": "My first test"}) as changeset_id:
print(f"Part of Changeset {changeset_id}")
node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}})
print(node1)
node2 = api.NodeCreate({"lon": 2, "lat": 2, "tag": {}})
node2 = api.node_create({"lon": 2, "lat": 2, "tag": {}})
print(node2)
way = api.WayCreate(
way = api.way_create(
{
"nd": [
node1["id"],
Expand Down
Loading