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

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. 

7 

8Example: 

9 

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 

14 

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. 

22 

23Package architecture 

24--------------------- 

25This package is split into four small, single-purpose modules, plus a 

26sub-package holding the grammar itself: 

27 

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. 

42 

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. 

54 

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. 

67 

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. 

77 

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""" 

86 

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 

101 

102configure_logging() 

103 

104__all__ = ["parse_adql", "parse_tree", "ast_nodes"]