Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
126 changes: 126 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Publish docs to GitHub Pages

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
# on change to watched file(s)
branches: [main]
paths:
- mkdocs/**
- mkdocs_external_files/**
- usajobsapi/**/*.py
- README.md
- CHANGELOG.md
- LICENSE
pull_request:
branches: [main] # PR verification only (base=main)
release:
types:
- published
workflow_dispatch:
inputs:
ref:
description: "Branch, tag, or SHA to deploy"
required: false
default: ""

permissions:
contents: read

jobs:
# Build job for PR verification
build:
if: github.event_name == 'pull_request'
name: Build docs
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/python-init

- name: Build MkDocs site
run: uv run mkdocs build -f mkdocs/mkdocs.yaml

- uses: ./.github/actions/python-post

publish:
# on release, push to main, or manual dispatch
if: >-
github.repository == 'paddy74/python-usajobsapi' &&
(
(
github.event_name == 'release' &&
github.event.release.prerelease == false &&
github.event.release.draft == false
) ||
(
github.event_name == 'push' &&
github.ref == 'refs/heads/main'
) ||
github.event_name == 'workflow_dispatch'
)
name: Publish docs
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deploy-pages.outputs.page_url }}

steps:
- name: Check out release tag
if: github.event_name == 'release'
uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ github.event.release.tag_name }}

- name: Check out branch
if: github.event_name == 'push'
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Resolve dispatch ref
if: github.event_name == 'workflow_dispatch'
id: dispatch-ref
run: |
ref="$DISPATCH_REF"
if [ -z "$ref" ]; then
if [ -n "$GITHUB_REF" ]; then
ref="$GITHUB_REF"
else
ref="$GITHUB_SHA"
fi
fi
echo "ref=$ref" >> "$GITHUB_OUTPUT"
env:
DISPATCH_REF: ${{ github.event.inputs.ref }}
- name: Check out dispatch ref
if: github.event_name == 'workflow_dispatch'
uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ steps.dispatch-ref.outputs.ref }}

- name: Setup GitHub Pages
uses: actions/configure-pages@v4

- uses: ./.github/actions/python-init
- name: Build static site
run: uv run mkdocs build -f mkdocs/mkdocs.yaml
- uses: ./.github/actions/python-post

- name: Upload site artifact
uses: actions/upload-pages-artifact@v3
with:
path: site

- name: Deploy to GitHub Pages
id: deploy-pages
uses: actions/deploy-pages@v4
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# python-usajobsapi

A Python wrapper for the [USAJOBS REST API](https://developer.usajobs.gov/). The package aims to provide a simple SDK interface for discovering and querying job postings from USAJOBS using Python.
[![PyPI][pypi-img]][pypi-lnk]
[![License][license-img]][license-lnk]
[![Tests][tests-img]][tests-lnk]
[![Python][python-img]][python-lnk]
[![Code Style][codestyle-img]][codestyle-lnk]
[![Coverage Status][codecov-img]][codecov-lnk]

A Python wrapper for the [USAJOBS REST API](https://developer.usajobs.gov/). The library aims to provide a simple interface for discovering and querying job postings from USAJOBS using Python.

## Features

Expand Down Expand Up @@ -73,3 +80,18 @@ This project is under active development and its API may change. Changes to the
## Contact

Questions or issues? Please open an issue on the repository's issue tracker.

<!-- Badges -->

[pypi-lnk]: https://pypi.org/p/python-usajobsapi
[pypi-img]: https://img.shields.io/pypi/v/python-usajobsapi.svg
[tests-lnk]: https://github.com/paddy74/python-usajobsapi/actions
[tests-img]: https://img.shields.io/github/actions/workflow/status/paddy74/python-usajobsapi/ci.yaml?logo=github&label=tests&branch=master
[codecov-lnk]: https://app.codecov.io/gh/paddy74/python-usajobsapi
[codecov-img]: https://app.codecov.io/gh/paddy74/python-usajobsapi/coverage.svg
[python-lnk]: https://img.shields.io/pypi/pyversions/python-usajobsapi.svg
[python-img]: https://pypi.python.org/pypi/python-usajobsapi
[codestyle-lnk]: https://docs.astral.sh/ruff
[codestyle-img]: https://img.shields.io/badge/code%20style-ruff-000000.svg
[license-lnk]: ./LICENSE
[license-img]: https://img.shields.io/pypi/l/python-usajobsapi?color=light-green&logo=gplv3&logoColor=white
17 changes: 17 additions & 0 deletions mkdocs/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Welcome to MkDocs

For full documentation visit [mkdocs.org](https://www.mkdocs.org).

## Commands

- `mkdocs new [dir-name]` - Create a new project.
- `mkdocs serve` - Start the live-reloading docs server.
- `mkdocs build` - Build the documentation site.
- `mkdocs -h` - Print help message and exit.

## Project layout

mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.
78 changes: 78 additions & 0 deletions mkdocs/gen_ref_pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Generate the code reference pages."""

import logging
from pathlib import Path, PurePosixPath

import mkdocs_gen_files

logger = logging.getLogger(__name__)


def gen_ref_pages(root_dir: Path, source_dir: Path, output_dir: str | Path) -> None:
"""Emit mkdocstrings-compatible reference pages and navigation entries.

:param root_dir: _description_
:type root_dir: Path
:param source_dir: _description_
:type source_dir: Path
:param output_dir: Output directory for the generated files; must be relative (non-escaping) to the docs directory.
:type output_dir: str | Path
:raises ValueError: _description_
:raises ValueError: _description_
:raises ValueError: _description_
"""

# output_dir must be a relative, non-escaping path
output_dir = Path(output_dir)
if output_dir.is_absolute():
raise ValueError("output_dir must be relative to the docs directory")
if any(part == ".." for part in output_dir.parts):
raise ValueError("output_dir must not traverse outside the docs directory")

root_dir = root_dir.resolve()
source_dir = source_dir.resolve()

# Exit early if source_dir has no .py files
py_files = sorted(source_dir.rglob("*.py"))
if not py_files:
raise ValueError(f"no Python modules found under {source_dir}")

nav = mkdocs_gen_files.Nav()

for path in py_files:
module_path = path.relative_to(source_dir).with_suffix("")
doc_path = path.relative_to(source_dir).with_suffix(".md")
full_doc_path = output_dir / doc_path

module_parts = module_path.parts

if module_parts[-1] == "__main__":
logger.debug("skip __main__ module: %s", path)
continue
if module_parts[-1] == "__init__":
module_parts = module_parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")

full_parts = (source_dir.name,) + module_parts
doc_uri = PurePosixPath(doc_path).as_posix() # normalize nav entries
nav[full_parts] = doc_uri

if not module_parts:
identifier = source_dir.name
else:
identifier = ".".join(full_parts)

with mkdocs_gen_files.open(full_doc_path, "w") as fd:
print("::: " + identifier, file=fd)
logger.debug("generated doc for %s -> %s", identifier, full_doc_path)

mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root_dir))

with mkdocs_gen_files.open("ref/NAV_REF.md", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())


root = Path(__file__).parent.parent
src = root / "usajobsapi"
gen_ref_pages(root, src, "ref")
10 changes: 10 additions & 0 deletions mkdocs/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from mkdocs.config.defaults import MkDocsConfig
from usajobsapi import _version as md


def on_config(config: MkDocsConfig):
config.site_name = f"{md.__title__} {md.__version__}"
config.site_author = md.__author__
config.copyright = f"Copyright &copy; {md.__copyright__}"

return config
48 changes: 48 additions & 0 deletions mkdocs/mkdocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
site_name: python-usajobsapi
site_url: https://paddy74.github.io/python-usajobsapi/
site_description: Project documentation for python-usajobsapi

site_dir: ../site

repo_url: https://github.com/paddy74/python-usajobsapi/
edit_uri: blob/main/docs/

theme:
name: readthedocs
locale: en
highlightjs: true

nav:
- Home: index.md
- Reference: ref/
- About:
- Changelog: about/changelog.md
- License: about/license.md

plugins:
- search
- gen-files:
scripts:
- gen_ref_pages.py
- literate-nav:
nav_file: NAV_REF.md
- section-index
- mkdocstrings
- external-files:
files:
- src: ../README.md
dest: README.md
- src: ../CHANGELOG.md
dest: about/changelog.md
- src: ../LICENSE
dest: about/license.md

markdown_extensions:
- toc:
baselevel: 2

hooks:
- hooks.py

watch:
- ../usajobsapi/_version.py
76 changes: 76 additions & 0 deletions mkdocs/mkdocs_external_files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# mkdocs-external-files

A lightweight [MkDocs](https://www.mkdocs.org/) plugin that allows you to directly include files outside your `docs_dir` into the site build.

## Features

- Pull individual files or glob patterns from anywhere outside your `docs_dir`.
- Resolve relative paths against the MkDocs configuration directory.
- Create real source `File` objects at build time.
- Automatically watch added sources during [live reload](https://www.mkdocs.org/user-guide/configuration/#live-reloading) (`mkdocs serve`).

## Installation

```bash
pip install mkdocs-external-files
# or with uv
uv pip install mkdocs-external-files
```

## Usage

- `src` accepts absolute paths or paths relative to the MkDocs config file.
- Glob patterns (`*`, `?`, `[]`) require `dest` to end with `/` to indicate a directory target.
- `dest` accepts relative paths to the `docs_dir`; during a build they are created in `site_dir`.

### Configuration

Enable the plugin and list the external sources inside `mkdocs.yml`:

```yaml
plugins:
- search
- external-files:
files:
- src: ../README.md
dest: extras/README.md
- src: ../assets/**
dest: extras/assets/
```

### Behavior

- `mkdocs serve`: Sources are streamed directly; nothing is copied into `docs_dir`, but live reload will watch the resolved absolute paths.
- `mkdocs build`: Virtual files are materialized into `site_dir`, so deployments that publish only the build output still include the added sources.
- Missing sources will result in a `FileNotFoundError` exception.

## Troubleshooting

If you are using [`mkdocs-gen-files`](https://github.com/oprypin/mkdocs-gen-files) then you _must_ place `mkdocs-external-files` after `mkdocs-gen-files` in your plugin settings.

```yaml
plugins:
- search
- gen-files:
scripts:
- gen_ref_pages.py
- external-files:
files:
- src: ../README.md
dest: extras/README.md
```

## Contributing

Contributions are welcome! To get started:

1. Fork the repository and create a new branch.
2. Create a virtual environment and install development dependencies.
3. Run the test suite with `pytest` and ensure all tests pass.
4. Submit a pull request describing your changes.

Please open an issue first for major changes to discuss your proposal.

## License

Distributed under the GNU General Public License v3.0. See [LICENSE](LICENSE) for details.
Loading