Coverage for src/pyadql/__init__.py: 100%
6 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-23 22:58 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-23 22:58 +0000
1# PyADQL - pyadql turns an ADQL query into an AST
2# Copyright (C) 2026 - Centre National d'Etudes Spatiales (Jean-Christophe Malapert for PDSSP)
3# This file is part of PyADQL <https://gitlab.cnes.fr/pdssp/common/pyadql>
4# SPDX-License-Identifier: Apache-2.0
5"""
6pyadql -- ADQL 2.1 parser producing a typed Python AST.
8Example:
10 >>> from pyadql import parse_adql
11 >>> tree = parse_adql("SELECT TOP 10 ra, dec FROM mytable WHERE ra > 10")
12 >>> tree.body.top
13 10
15Note the root node is a ``SelectExpression``, not the ``SELECT`` itself: per
16ADQL 2.1's own grammar, ``ORDER BY``/``OFFSET``/``WITH`` apply to the whole
17combined query (after any ``UNION``/``EXCEPT``/``INTERSECT``), not to an
18individual ``SELECT`` -- so ``tree.body`` holds the actual query (a
19``Query`` or a ``SetOperation``), while ``tree.order_by``/``tree.offset``/
20``tree.with_clause`` live on ``tree`` itself. See ``ast_nodes.SelectExpression``'s
21docstring for the full shape.
23Package architecture
24---------------------
25This package is split into four small, single-purpose modules, plus a
26sub-package holding the grammar itself:
28- ``grammar/`` (``lexer.lark``, ``literals.lark``, ``core.lark``, ``adql.lark``)
29 The ADQL 2.1 grammar, written for the `Lark <https://github.com/lark-parser/lark>`_
30 parsing library (Earley engine), transcribed directly from Annex A of
31 IVOA Recommendation ADQL 2.1. Pure grammar text (no Python), loaded and
32 compiled lazily on first use by ``parser._get_lark_parser()`` from
33 ``grammar/adql.lark`` (the entry point, which assembles the other three
34 files). Every rule is commented with a cross-reference to the exact
35 Annex A production it implements (``[AnnexA #x]``). ``core.lark`` holds
36 the mutually-recursive heart of the grammar (query structure,
37 predicates, value expressions, functions, geometry) in one file quite
38 deliberately -- see that file's own header comment for the Lark
39 constraints (experimentally confirmed) that make a deeper split unsafe.
40 This is the place to look first when adding support for a new piece of
41 ADQL syntax.
43- ``ast_nodes``
44 Defines every AST node as a plain ``@dataclass``: ``SelectExpression``
45 (the root), ``Query``, ``SetOperation``, ``CTE``, ``TableRef``,
46 ``Join``, ``BinaryOp``, ``Between``, ``InPredicate``, ``Like``,
47 ``IsNull``, ``Exists``, ``Cast``/``CastType``, ``Coalesce``, the
48 geometry nodes ``Point``/``Circle``/``Box``/``Polygon``/``Coordinates``/
49 ``Region``/``Contains``/``Intersects``/``Distance``/etc., and more.
50 This module has **no dependency on Lark**: the AST it defines can be
51 built, inspected, serialized, and tested completely independently of
52 the parser, which keeps the public data model stable even if the
53 parsing engine were ever swapped out.
55- ``parser``
56 The bridge between the grammar and the AST: compiles the multi-file
57 Lark grammar (``_get_lark_parser()``, lazy + cached, resolving
58 ``grammar/adql.lark``'s relative ``%import`` statements via
59 ``Lark.open(..., rel_to=...)``) and defines ``ADQLTransformer``, a
60 ``lark.Transformer`` with one method per grammar rule that turns each
61 Lark parse-tree node into the matching ``ast_nodes`` dataclass. Exposes
62 the two public entry points re-exported below: ``parse_adql`` (query ->
63 typed AST) and ``parse_tree`` (query -> raw Lark tree, mainly useful for
64 debugging the grammar itself). Also emits structured ``loguru`` debug
65 logs (grammar-compilation time, Lark-parsing time, AST-building time,
66 AST summary) that the CLI's ``-v``/``-vv``/``-vvv`` flags surface.
68- ``cli``
69 The ``pyadql`` command-line entry point (declared in ``pyproject.toml``
70 under ``[project.scripts]``). A thin ``argparse``-based wrapper around
71 ``parse_adql``/``parse_tree`` that reads a query from an argument, a
72 file, or stdin, prints the AST (as a Python repr, JSON, or a raw Lark
73 tree), and configures ``loguru`` logging verbosity. Not imported by the
74 two lines below -- it depends on the rest of the package, not the other
75 way around, so importing ``pyadql`` as a library never pulls in
76 ``argparse``/CLI-only concerns.
78Data flow for a single call to ``parse_adql(query)``:
79 query (str)
80 -> Lark parser (``grammar/adql.lark`` + imports, compiled once, cached)
81 -> raw Lark parse tree
82 -> ``ADQLTransformer`` (``parser.py``)
83 -> typed AST (``ast_nodes.SelectExpression``, wrapping a ``Query`` or
84 ``SetOperation`` in ``.body``)
85"""
87from . import ast_nodes
88from ._version import (
89 __author__,
90 __author_email__,
91 __copyright__,
92 __description__,
93 __license__,
94 __name_soft__,
95 __title__,
96 __url__,
97 __version__,
98)
99from .config import configure_logging
100from .parser import parse_adql, parse_tree
102configure_logging()
104__all__ = ["parse_adql", "parse_tree", "ast_nodes"]