Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 1 addition & 15 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
---
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

build:
os: ubuntu-22.04
tools:
python: "3.12"

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py

# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml

# Optionally build your docs in additional formats such as PDF and ePub
formats: all

# Optionally set the version of Python and requirements required to build your docs
python:
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
- path: .
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ To learn about how to use the OpenAEV Python client and read some examples and c

### API reference

To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://openaev-client-for-python.readthedocs.io/en/latest/pyoaev/pyoaev.html).
To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://openaev-client-for-python.readthedocs.io/en/latest/autoapi/index.html).

## Tests

Expand Down
Empty file added docs/_static/.gitkeep
Empty file.
69 changes: 29 additions & 40 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,54 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys

sys.path.insert(0, os.path.abspath(".."))


# -- Project information -----------------------------------------------------

project = "OpenAEV client for Python"
copyright = "2024, Filigran"
author = "OpenAEV Project"

# The full version, including alpha/beta/rc tags
release = "1.10.1"

master_doc = "index"

autoapi_modules = {"pyoaev": {"prune": True}}

pygments_style = "sphinx"

# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.inheritance_diagram",
"autoapi.sphinx",
"autoapi.extension",
"sphinx_autodoc_typehints",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
autoapi_dirs = ["../pyoaev"]
autoapi_options = [
"members",
"undoc-members",
"private-members",
"show-inheritance",
]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
autodoc_inherit_docstrings = False

# Inherited docstrings from stdlib base classes are noisy — suppress them
_INHERITED_DOCSTRING_MARKERS = [
"Create a collection of name/value pairs.", # enum.Enum
"str(object='') -> str", # str
]

# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
def _suppress_inherited_docstring(app, what, name, obj, options, lines):
"""Remove inherited stdlib docstrings from generated API docs."""
if what == "class" and lines:
joined = "\n".join(lines)
if any(marker in joined for marker in _INHERITED_DOCSTRING_MARKERS):
lines.clear()


def setup(app):
app.connect("autodoc-process-docstring", _suppress_inherited_docstring)

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
5 changes: 0 additions & 5 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
OpenAEV client for Python
=========================

The pyoaev library is designed to help OpenAEV users and developers to interact
with the OpenAEV platform API.

The Python library requires Python >= 3.

.. toctree::
:maxdepth: 2
:caption: Contents:

client_usage/getting_started.rst
pyoaev/pyoaev


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
7 changes: 4 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
autoapi==2.0.1
sphinx==8.2.3
sphinx==9.1.0
sphinx-autoapi==3.4.0
astroid==3.3.8
sphinx-autodoc-typehints==3.2.0
sphinx_rtd_theme==3.0.2
sphinx_rtd_theme==3.1.0
4 changes: 2 additions & 2 deletions pyoaev/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union

import requests
from pythonjsonlogger import jsonlogger
from pythonjsonlogger.json import JsonFormatter


class _StdoutStream:
Expand Down Expand Up @@ -116,7 +116,7 @@ def validate_attrs(
)


class CustomJsonFormatter(jsonlogger.JsonFormatter):
class CustomJsonFormatter(JsonFormatter):
def add_fields(self, log_record, record, message_dict):
super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
if not log_record.get("timestamp"):
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"PyYAML (>=6.0,<6.1)",
"pydantic (>=2.11.3,<2.12.0)",
"pydantic-settings (>=2.11.0,<2.12.0)",
"requests (>=2.32.3,<2.33.0)",
"requests (>=2.33.1,<2.34.0)",
"setuptools (>=80.9.0,<80.10.0)",
"cachetools (>=5.5.0,<5.6.0)",
"prometheus-client (>=0.22.1,<0.23.0)",
Expand All @@ -47,7 +47,7 @@ dependencies = [

[project.optional-dependencies]
dev = [
"black (>=25.11.0,<25.12.0)",
"black (>=25.12.0,<25.13.0)",
"build (>=1.3.0,<1.4.0)",
"isort (>=6.1.0,<6.2.0)",
"types-pytz (>=2025.2.0.20250326,<2025.3.0.0)",
Expand Down Expand Up @@ -96,3 +96,8 @@ warn_redundant_casts = true
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true

[tool.coverage.run]
omit = [
"test/*",
]
106 changes: 106 additions & 0 deletions test/configuration/test_connector_config_schema_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import unittest
from unittest.mock import MagicMock

from pyoaev.configuration.connector_config_schema_generator import (
ConnectorConfigSchemaGenerator,
)


class TestConnectorConfigSchemaGenerator(unittest.TestCase):
def test_dereference_schema_resolves_internal_refs(self):
schema = {
"$defs": {
"Item": {
"type": "object",
"properties": {"value": {"type": "string"}},
}
},
"type": "object",
"properties": {
"item": {"$ref": "#/$defs/Item"},
"items": {"type": "array", "items": [{"$ref": "#/$defs/Item"}]},
},
}

resolved = ConnectorConfigSchemaGenerator.dereference_schema(schema)

self.assertEqual(resolved["properties"]["item"]["type"], "object")
self.assertIn("value", resolved["properties"]["item"]["properties"])
self.assertEqual(
resolved["properties"]["items"]["items"][0]["properties"]["value"]["type"],
"string",
)

def test_dereference_schema_rejects_unsupported_refs(self):
with self.assertRaises(ValueError):
ConnectorConfigSchemaGenerator.dereference_schema(
{"$ref": "external://schema"}
)

def test_flatten_config_loader_schema_and_filter_schema(self):
root_schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "config.schema.json",
"additionalProperties": False,
"properties": {
"connector": {
"properties": {
"name": {"type": "string", "title": "Name"},
"id": {"type": "string"},
},
"required": ["name"],
}
},
}

flattened = ConnectorConfigSchemaGenerator.flatten_config_loader_schema(
root_schema
)
flattened["properties"]["CONNECTOR_ID"] = {"type": "string"}
flattened["required"].append("CONNECTOR_ID")

filtered = ConnectorConfigSchemaGenerator.filter_schema(flattened)

self.assertEqual(filtered["additionalProperties"], False)
self.assertIn("CONNECTOR_NAME", filtered["properties"])
self.assertNotIn("title", filtered["properties"]["CONNECTOR_NAME"])
self.assertIn("CONNECTOR_NAME", filtered["required"])
self.assertNotIn("CONNECTOR_ID", filtered["properties"])
self.assertNotIn("CONNECTOR_ID", filtered["required"])

def test_flatten_config_loader_schema_defaults_additional_properties_to_true(self):
root_schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "config.schema.json",
"properties": {"app": {"properties": {}, "required": []}},
}

flattened = ConnectorConfigSchemaGenerator.flatten_config_loader_schema(
root_schema
)

self.assertTrue(flattened["additionalProperties"])

def test_nullable_schema_returns_null_when_inner_schema_is_null(self):
generator = ConnectorConfigSchemaGenerator(by_alias=True)
generator.generate_inner = MagicMock(return_value={"type": "null"})

result = generator.nullable_schema(
{"type": "nullable", "schema": {"type": "str"}}
)

self.assertEqual(result, {"type": "null"})

def test_nullable_schema_returns_inner_schema_when_not_null(self):
generator = ConnectorConfigSchemaGenerator(by_alias=True)
generator.generate_inner = MagicMock(return_value={"type": "string"})

result = generator.nullable_schema(
{"type": "nullable", "schema": {"type": "str"}}
)

self.assertEqual(result, {"type": "string"})


if __name__ == "__main__":
unittest.main()
Loading
Loading