Skip to content

Commit 756c3fa

Browse files
committed
Define the public API with __all__ and freeze it
The versioning policy said "documented non-underscore names are public", while Sphinx ran with undoc-members, so any helper that happened to have a docstring became part of the permanent 1.x surface. Each module now declares __all__ -- the curated set of names psycodict actually promises -- and the API reference documents exactly those (undoc-members is off). A non-__all__ name, docstring or not, is implementation: still importable, so nothing downstream breaks, but not promised. tests/test_public_api.py freezes every module's __all__, checks each exported name resolves, checks `from psycodict import *` binds exactly the root set, and checks the specific names LMFDB and seminars import still resolve -- including private ones (seminars' _counts_cols, _meta_*_cols) and a couple kept importable but unpromised (range_formatter, KeyedDefaultDict), since __all__ governs `import *` and the docs, not explicit imports. Versioning.md is rewritten around this: public = the __all__ names and their documented behavior; db[name] is the canonical table lookup while db.<name> is convenience a real database attribute wins over, so adding a method in a minor release never makes a table unreachable through db[name].
1 parent 3b2ed19 commit 756c3fa

18 files changed

Lines changed: 244 additions & 12 deletions

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,17 @@ hardening standalone use; the highlights:
464464
install smoke tests assert `importlib.metadata.version == __version__` and run
465465
`pip check` for both a plain and a binary-extra install.
466466

467+
- **The public API is defined by `__all__`.** Each module now declares the
468+
names psycodict promises to keep across 1.x, and the API reference documents
469+
exactly those (Sphinx `undoc-members` is off). A non-`__all__` name -- even one
470+
with a docstring -- is implementation: still importable, so nothing downstream
471+
breaks, but not part of the stability promise. `tests/test_public_api.py`
472+
freezes the surface so a change to it is deliberate. Versioning.md is rewritten
473+
around this, and states that `db[name]` is the canonical table lookup while
474+
`db.<name>` is convenience syntax a real database attribute wins over -- so
475+
adding a method in a minor release never makes a table unreachable through
476+
`db[name]`.
477+
467478
### Release candidates
468479

469480
1.0.0 is published as a sequence of release candidates first. `pip` ignores

‎Versioning.md‎

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ metadata tables living inside your database.
99

1010
## What is public
1111

12-
* **Non-underscore names** in the `psycodict` package that are documented — in
13-
the specification documents ([QueryLanguage.md](QueryLanguage.md),
14-
[Searching.md](Searching.md), [DataManagement.md](DataManagement.md),
15-
[MetadataFormats.md](MetadataFormats.md)) or in docstrings. Names with a
16-
leading underscore are private, whatever module they live in, and may change
17-
in any release.
12+
* **The names each module exports in its `__all__`**, and their documented
13+
behavior. These are exactly the names in the [API reference](api/index.md),
14+
and the snapshot test `tests/test_public_api.py` freezes them, so the promise
15+
and the code cannot drift apart. A non-underscore name that is *not* in an
16+
`__all__` (and every underscore-prefixed name, whatever module it lives in)
17+
is implementation: it may still be importable, but it is not part of this
18+
promise and may change in any release. Having a docstring does not make a
19+
name public; being in `__all__` does.
1820
* **The query language** as specified in [QueryLanguage.md](QueryLanguage.md):
1921
the meaning of a query dictionary is stable within a major version: new
2022
features may be added in minor versions, but functioning queries will
@@ -34,13 +36,24 @@ metadata tables living inside your database.
3436
* **The `meta_*` tables**, whose layout is governed by the metadata format
3537
protocol below.
3638

39+
## Reaching a table
40+
41+
`db[name]` is the canonical, collision-free way to reach a search table, and the
42+
one covered by this promise. `db.<name>` (attribute access) is convenience
43+
syntax for the same lookup, with one caveat: a real attribute or method of the
44+
database object wins over a table of the same name, so a table called `config`
45+
or `tablenames` is reachable only through `db["config"]`. Adding a method to
46+
the database class in a minor release therefore never makes a table inaccessible
47+
through the canonical `db[name]` lookup, even if it shadows an attribute-access
48+
name.
49+
3750
## What is not covered
3851

39-
Underscore-prefixed names; the exact SQL text psycodict emits (only its
40-
semantics); performance characteristics; the contents of log files; and
41-
undocumented behavior generally, even where observable. If something
42-
undocumented matters to your project, open an issue — turning it into
43-
documented (hence stable) behavior is usually easy.
52+
Non-`__all__` names, whether or not they carry a docstring; underscore-prefixed
53+
names; the exact SQL text psycodict emits (only its semantics); performance
54+
characteristics; the contents of log files; and undocumented behavior generally,
55+
even where observable. If something undocumented matters to your project, open
56+
an issue — turning it into documented (hence stable) behavior is usually easy.
4457

4558
## Database metadata compatibility
4659

‎docs/conf.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@
5252
# documentation conventions (INPUT:/OUTPUT: bullet blocks, EXAMPLES:: with
5353
# literal transcripts); those are plain reST, so autodoc renders them as-is.
5454
autodoc_member_order = "bysource"
55+
# No "undoc-members": the API reference documents exactly each module's __all__
56+
# (its supported public surface), not every non-underscore name that happens to
57+
# have -- or lack -- a docstring. Versioning.md defines the public API as the
58+
# __all__ names, so the reference and the promise stay in step.
5559
autodoc_default_options = {
5660
"members": True,
57-
"undoc-members": True,
5861
"show-inheritance": True,
5962
}
6063

‎psycodict/__init__.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,17 @@
5252
from psycopg.sql import SQL, Identifier, Placeholder, Literal, Composable, Composed
5353

5454
assert SQL and Identifier and Placeholder and Literal and Composable and Composed
55+
56+
# The names psycodict re-exports at the package root. The Postgres* classes and
57+
# the rest of the interface are imported from their submodules; see each
58+
# module's __all__ and Versioning.md for the full public API.
59+
__all__ = [
60+
"__version__",
61+
"SQL",
62+
"Identifier",
63+
"Placeholder",
64+
"Literal",
65+
"Composable",
66+
"Composed",
67+
"DelayCommit",
68+
]

‎psycodict/base.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@
5050
validate_column_type,
5151
)
5252

53+
# The supported public API of this module: the names psycodict promises to
54+
# keep across 1.x. Other non-underscore names are implementation that may
55+
# change; see Versioning.md.
56+
__all__ = [
57+
"PostgresBase",
58+
]
59+
5360

5461
##################################################################
5562
# meta_* infrastructure #

‎psycodict/config.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
from collections import defaultdict
1818
from copy import deepcopy
1919

20+
# The supported public API of this module: the names psycodict promises to
21+
# keep across 1.x. Other non-underscore names are implementation that may
22+
# change; see Versioning.md.
23+
__all__ = [
24+
"Configuration",
25+
]
26+
2027

2128
def strbool(s):
2229
"""

‎psycodict/database.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
from .searchtable import PostgresSearchTable
5656
from .utils import DelayCommit, safe_child_path
5757

58+
# The supported public API of this module: the names psycodict promises to
59+
# keep across 1.x. Other non-underscore names are implementation that may
60+
# change; see Versioning.md.
61+
__all__ = [
62+
"PostgresDatabase",
63+
]
64+
5865
# The registry of metadata-format migrations. Entry N describes the step
5966
# from format N-1 to format N; MetadataFormats.md has the checklist a new
6067
# format must follow.

‎psycodict/dbdiff.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424

2525
from psycopg.sql import SQL, Identifier
2626

27+
# The supported public API of this module: the names psycodict promises to
28+
# keep across 1.x. Other non-underscore names are implementation that may
29+
# change; see Versioning.md.
30+
__all__ = [
31+
"compare_databases",
32+
"format_differences",
33+
]
34+
2735
# The columns of meta_tables that are compared for tables present in both
2836
# databases (in this order). The remaining columns are deliberately left
2937
# out: total is reported through row_counts instead, out_of_order and

‎psycodict/encoding.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
import datetime
99
import math
1010
from psycopg.adapt import Dumper
11+
12+
# The supported public API of this module: the names psycodict promises to
13+
# keep across 1.x. Other non-underscore names are implementation that may
14+
# change; see Versioning.md.
15+
__all__ = [
16+
"Json",
17+
"Array",
18+
"copy_dumps",
19+
]
1120
try:
1221
try:
1322
# this fails in sage 9.3

‎psycodict/grants.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626

2727
from .validation import InvalidDefinitionError, MAX_IDENTIFIER_LENGTH
2828

29+
# The supported public API of this module: the names psycodict promises to
30+
# keep across 1.x. Other non-underscore names are implementation that may
31+
# change; see Versioning.md.
32+
__all__ = [
33+
"GrantPolicy",
34+
"LMFDBGrantPolicy",
35+
]
36+
2937
# The actions a policy can grant. A policy manages exactly these: privileges
3038
# outside this set (TRUNCATE, REFERENCES, TRIGGER) are left alone.
3139
GRANT_ACTIONS = ("SELECT", "INSERT", "UPDATE", "DELETE")

0 commit comments

Comments
 (0)