Skip to content

Fix SDK link, Python version contracts, test separation, token…#240

Merged
aaront merged 12 commits into
mainfrom
2026-spring-cleaning
Jul 15, 2026
Merged

Fix SDK link, Python version contracts, test separation, token…#240
aaront merged 12 commits into
mainfrom
2026-spring-cleaning

Conversation

@aaront

@aaront aaront commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Spring Cleaning PR

Summary

Four independent improvements to package quality, CI reliability, and security — targeting customers who begin integrations through this package.


1. SDK documentation link

  • README.rst: replace legacy https://geotab.github.io/sdk/ with https://developers.geotab.com/
  • README.rst: fix docs URL to HTTPS canonical form (readthedocs.io/en/latest)

2. Python version reconciliation (3.10–3.14)

Drops Python 3.9 (EOL October 2025) and adds 3.14. Every version contract now agrees:

Location Before After
setup.py runtime guard < (3, 7, 0) < (3, 10, 0)
setup.py python_requires missing >=3.10
setup.py classifiers 3.9–3.13 3.10–3.14
setup.py packages list mygeotab, mygeotab.ext + mygeotab.altitude (was silently absent from all installs)
mypy.ini python_version 9 (invalid) 3.10
pyproject.toml ruff target-version py39 py310
setup.cfg [bdist_wheel] universal = 1 (Python 2 artifact) removed
README.rst "Python 3.9+" "Python 3.10+"

3. Test separation — offline vs credentialed, and full version matrix

pytest.ini — two new marks with descriptions; addopts excludes both by default so pytest never needs network access or credentials:

markers =
    live: real network calls to public Geotab servers (no credentials)
    integration: requires MYGEOTAB_DATABASE / USERNAME / PASSWORD
addopts = -m "not live and not integration"

tests/test_api_live.pypytestmark = pytest.mark.live applied.

.github/workflows/pythonpackage.yml — single job replaced with three:

Job Python Secrets Runs
test-offline 3.10 / 3.11 / 3.12 / 3.13 / 3.14 (matrix) none all unit tests
test-live 3.12 none unauthenticated GetVersion calls
test-integration 3.12 DB credentials credentialed tests; exits 0 when no tests collected (exit code 5) or secrets absent

4. Session token file protection

mygeotab/cli.py — enforces strict filesystem permissions on every write:

  • Config directory: os.chmod(..., stat.S_IRWXU)0700 (owner only)
  • Config file: os.chmod(..., stat.S_IRUSR | stat.S_IWUSR)0600 (owner read/write only)
  • Applied in both Session.save() and Session.logout()
  • myg --help now includes guidance: "To remove a saved session run: myg sessions remove <database>"

5. Coverage: 59% → 81% (+22 pp)

835 lines of new tests across 5 files; 163 tests pass, 0 failures.

File Before After
altitude/daas_definition.py 0% 100%
exceptions.py ~90% 100%
parameters.py ~95% 100%
altitude/wrapper.py ~15% 83%
api.py ~65% 83%
ext/entitylist.py ~60% 73%

New file — tests/test_daas_definition.py: full branch coverage of DaasError, DaasResult (None/empty/missing-apiResult guards, gateway errors, apiResult errors, "error" singular field, errorMessage as string/dict/empty), DaasGetJobStatusResult (all four has_finished() outcomes, errorResult override, missing status key), DaasGetQueryResult.

Extended — tests/test_altitude.py: _extract_errors (both branches), call_api (success, invalid name, non-retry exception), create_job (success, errors in response, re-raise), check_job_status, wait_for_job_to_complete (immediate done, polling, error path), fetch_data (single page, not-finished guard, multi-page), get_data, do (full orchestration).

Extended — tests/test_api_call.py: authenticate() ExtendSession path, "ThisServer" redirect, no-path-in-result, DbUnavailableException (Initializing + UnknownDatabase), other-exception re-raise; call() re-auth on InvalidUserException when password present, __reauthorize_count guard prevents infinite loop; multi_call with no params element.

Extended — tests/test_api.py: _process() unknown-structure passthrough; _server fallback to my.geotab.com; MyGeotabException.data field.

Extended — tests/test_api_entitylist.py: first/last on empty list, entity with 0 items, __add__ plain list, __radd__, __mul__/__rmul__, __copy__, to_dataframe ImportError, EntityListAPI.get return type.


Commits

SHA Message
6e24073 chore: fix SDK link, Python version contracts, test separation, token file permissions
9854eab fix(ci): treat pytest exit 5 (no integration tests) as success
3c955ce test: raise offline coverage from 59% to 81%
c7ee84f chore: expand supported Python range to 3.10–3.14

… file permissions

- README.rst: update SDK link geotab.github.io/sdk -> developers.geotab.com;
  fix docs URL to HTTPS canonical form (readthedocs.io/en/latest)
- setup.py: correct runtime guard 3.7->3.9 (typo in version + message);
  add python_requires='>=3.9' for pip-level enforcement;
  add mygeotab.altitude to packages list (was silently missing from installs)
- setup.cfg: remove bdist_wheel universal=1 (Python 2 artifact)
- mypy.ini: fix python_version=9 -> 3.9 (was an invalid value)
- pytest.ini: register live/integration marks; addopts skips them by default
  so plain 'pytest' never needs network or credentials
- tests/test_api_live.py: apply pytestmark=pytest.mark.live
- .github/workflows/pythonpackage.yml: split single job into three:
    test-offline  - matrix 3.9/3.10/3.11/3.12/3.13, no secrets
    test-live     - unauthenticated GetVersion calls, Python 3.12
    test-integration - credentialed, Python 3.12, skips if secrets absent
- mygeotab/cli.py: enforce 0700 on app config dir and 0600 on config.ini
  on every write (save/logout); add logout guidance to main --help
No tests are currently marked @pytest.mark.integration, so the
test-integration job collected 0 items and exited with code 5.
The shell one-liner now passes when code==5 and re-raises for
any other non-zero exit.
New file:
- tests/test_daas_definition.py — full coverage of the previously
  untested daas_definition module (DaasError, DaasResult,
  DaasGetJobStatusResult, DaasGetQueryResult); hits all branches
  including the NOT_FULL_API_CALL_EXCEPTION guard, errorMessage
  variants, and all four has_finished() outcomes

Extended:
- tests/test_altitude.py — AltitudeAPI wrapper methods now tested:
  _extract_errors (both branches), call_api (success, invalid name,
  non-retry exception), create_job (success, errors, re-raise),
  check_job_status, wait_for_job_to_complete (immediate done, polling,
  error path), fetch_data (single page, not-finished guard,
  multi-page), get_data (combined pages, empty), do (orchestration)

- tests/test_api_call.py — authentication edge cases:
  ExtendSession path (session_id + no password), 'ThisServer' redirect,
  no-path-in-result, DbUnavailableException (Initializing +
  UnknownDatabase), other-exception re-raise; call() re-auth when
  password is present, __reauthorize_count guard prevents infinite
  loop; multi_call with single-element (no params)

- tests/test_api.py — _process() unknown-structure passthrough;
  _server fallback to my.geotab.com when credentials.server is None;
  MyGeotabException.data field

- tests/test_api_entitylist.py — first/last on empty list, entity
  with 0 items, __add__ with plain list, __radd__, __mul__, __rmul__,
  __copy__, to_dataframe ImportError, entitylist.API.get return type

Coverage by file (offline suite):
  daas_definition.py  0% → 100%
  exceptions.py      ~90% → 100%
  parameters.py      ~95% → 100%
  altitude/wrapper.py ~15% → 83%
  api.py             ~65% → 83%
  ext/entitylist.py  ~60% → 73%
  TOTAL               59% → 81%
- Drop Python 3.9 (EOL October 2025)
- Add Python 3.14 (released October 2025)
- setup.py: runtime guard (3,9,0)→(3,10,0); python_requires >=3.10;
  remove 3.9 classifier, add 3.14 classifier
- mypy.ini: python_version 3.9→3.10
- pyproject.toml: ruff target-version py39→py310
- README.rst: 'Python 3.9+' → 'Python 3.10+'
- CI: test-offline matrix [3.9,3.10,3.11,3.12,3.13]→[3.10,3.11,3.12,3.13,3.14]
usage.rst:
- Replace all my.geotab.com/sdk/#/... links with developers.geotab.com
- Fix mygeotab.api() -> mygeotab.API() (wrong case)
- Fix undefined saved_session_id -> local_credentials.session_id
- Fix 'a operating' -> 'an operating'
- Remove stray underscore from API reference link label

commandline.rst:
- Remove Python 3.5.2 / Anaconda example (replace with current output)
- Fix 'sesison' typo
- Remove vague 'More functionality will be added' placeholder
- Add security note: 0700/0600 file permissions, how to clear sessions
- Add --list flag to sessions example
- Add sessions --help to help section

altitude.rst:
- Replace broken automodule::mygeotab.altitude (empty __init__)
  with explicit autoclass directives for AltitudeAPI, DaasResult,
  DaasGetJobStatusResult, DaasGetQueryResult, DaasError
- Update link from its.geotab.com to developers.geotab.com

conf.py:
- Replace deprecated datetime.utcnow() with datetime.now(timezone.utc)
usage.rst:
- Getting Started -> /myGeotab/guides/gettingStarted/
- Concepts guide -> /myGeotab/guides/concepts/ (x2)
- API methods reference -> /myGeotab/apiReference/methods/
- API objects reference -> /myGeotab/apiReference/objects/

altitude.rst:
- its.geotab.com/altitude/ (308 redirect) -> altitude.geotab.com/altitude/
Source files:
- api_async.py: remove stray '.abs' from call_async() docstring
- exceptions.py: fix typo 'excepton' -> 'exception' in TimeoutException

docs/usage.rst:
- Fix 'authenticted' -> 'authenticated'
- Replace nonexistent OverLimitException with correct MyGeotabException
- Fix code-block language 'javascript' -> 'json' for JSON sample
- Fix double 'The the' -> 'The'
- Add 'Batching Calls' section documenting multi_call()
- Add 'Async Usage' section with asyncio.run() example and
  list of all _async method variants

docs/api.rst:
- Switch autodoc from mygeotab.api.API (sync base) to
  mygeotab.api_async.API (the class actually exported at top level);
  async methods are now visible in the reference
- Add server_call() and server_call_async() to the reference
- Add intro note clarifying the exported class supports both interfaces

docs/ext.rst:
- Add EntityList prose section with sort_by, first/last, entity,
  to_dataframe() examples
- Replace bare '/examples' path with GitHub hyperlink
- Remove stale 'currently experimental' disclaimer

docs/commandline.rst:
- Document --user/-u, --password/-p, --server inline flags
- State config file paths (Linux and macOS)
- Document console locals (myg, mygeotab, dates)
- Add IPython/ptpython enhancement note with install instructions
- Remove hardcoded version number from example output

docs/altitude.rst:
- Add conceptual intro explaining what Altitude is and how auth works
- Add Workflow section with step-by-step and do() shortcut example

docs/conf.py:
- Remove 2014 sphinx-quickstart comment block
- Uncomment and set needs_sphinx = '7.0' (Furo/sphinx-copybutton minimum)
- Rename deprecated master_doc -> root_doc (Sphinx 4.0+)
- Replace placeholder Texinfo description with real one

docs/index.rst:
- Remove 2014 sphinx-quickstart comment block
@aaront

aaront commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@sanyamguptageotab Would you be able to review the documentation changes for the altitude.rst file?

@aaront aaront merged commit 646d615 into main Jul 15, 2026
9 checks passed
@aaront aaront deleted the 2026-spring-cleaning branch July 15, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant