Skip to content

Commit

Permalink
Merge pull request #12 from RazerM/feature/sqlalchemy-2.0-only
Browse files Browse the repository at this point in the history
SQLAlchemy 2.0+ and typing
  • Loading branch information
RazerM authored Jan 6, 2024
2 parents a8a2467 + 1c6f917 commit 9f715dd
Show file tree
Hide file tree
Showing 21 changed files with 801 additions and 253 deletions.
16 changes: 8 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-toml
- id: check-yaml
Expand All @@ -10,29 +10,29 @@ repos:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
rev: v3.1.0
hooks:
- id: prettier
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/timothycrosley/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 7.0.0
hooks:
- id: flake8
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.12.1
hooks:
- id: black
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.13.0
rev: 1.16.0
hooks:
- id: blacken-docs
additional_dependencies:
- black==23.1.0
- black==23.12.1
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@

## [Unreleased][unreleased]

**This release supports SQLAlchemy 2.0 or later**

### Changed

- Python 3.7 or later is required.
- The following arguments for `grant` and `revoke` are now keyword-only:
- `grant_option`
- `schema`
- `arg_types`
- `quote_subname`
- The following arguments for `as_grant_statements` and `as_revoke_statements`
are now keyword-only:
- `schema`
- `arg_types`
- `quote_subname`
- The `type_` parameter for `as_grant_statements` and `as_revoke_statements` was
renamed to `type`.
- Installing `pg-grant[sqlalchemy]` no longer installs psycopg2. Make sure you
depend on a driver like `sqlalchemy[postgresql]` (psycopg2) or
`sqlalchemy[postgresql_psycopg]` (psycopg 3).

### Removed

Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ recursive-include tests *.sql
recursive-include docs *
prune docs/_build
include *-requirements.txt
include src/pg_grant/py.typed
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
# intersphinx
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"psycopg2": ("https://www.psycopg.org/docs/", None),
"psycopg": ("https://www.psycopg.org/psycopg3/docs", None),
}

autodoc_member_order = "bysource"
Expand Down
23 changes: 20 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]
dependencies = ["attrs"]
dependencies = [
"attrs>=21.3",
"typing_extensions; python_version<'3.9'"
]

[project.optional-dependencies]
sqlalchemy = ["sqlalchemy[postgresql]>=1.4"]
test = ["plumbum", "pytest"]
sqlalchemy = ["sqlalchemy>=2"]
test = ["plumbum", "pytest", "sqlalchemy[postgresql_psycopg]>=2"]
docs = ["pg_grant[sqlalchemy]", "sphinx>=6", "furo"]
docstest = ["pg_grant[docs]", "doc8"]
pep8test = ["flake8", "pep8-naming"]
Expand All @@ -40,3 +43,17 @@ ignore-path = "docs/_build/"
[tool.isort]
profile = "black"
known_first_party = ["pg_grant"]

[tool.mypy]
warn_unused_configs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
warn_unused_ignores = true
warn_return_any = true
no_implicit_reexport = true
strict_equality = true
31 changes: 31 additions & 0 deletions src/pg_grant/_typing_sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import sys
from typing import Any, List, Tuple, Type, Union

from sqlalchemy import Executable
from sqlalchemy import Sequence as SequenceType
from sqlalchemy import TableClause
from sqlalchemy.orm import Mapper

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias


ExecutableType: TypeAlias = Executable
TableTarget: TypeAlias = Union[
TableClause,
Type[Any],
Mapper[Any],
]

AnyTarget: TypeAlias = Union[TableTarget, SequenceType, str]
ArgTypesInput: TypeAlias = Union[List[str], Tuple[str, ...]]

__all__ = (
"AnyTarget",
"ArgTypesInput",
"ExecutableType",
"TableTarget",
"SequenceType",
)
23 changes: 12 additions & 11 deletions src/pg_grant/parse.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import List, Optional, Sequence
from typing import List, Optional, Tuple, Union

from .types import PgObjectType, Privileges


def _get_acl_username(acl):
def _get_acl_username(acl: str) -> Tuple[int, str]:
"""Port of ``copyAclUserName`` from ``dumputils.c``"""
i = 0
output = ""
Expand Down Expand Up @@ -80,7 +80,7 @@ def get_default_privileges(type: PgObjectType, owner: str) -> List[Privileges]:


def parse_acl(
acl: Sequence[str],
acl: Union[List[str], Tuple[str, ...]],
type: Optional[PgObjectType] = None,
subname: Optional[str] = None,
) -> List[Privileges]:
Expand Down Expand Up @@ -113,14 +113,15 @@ def parse_acl_item(
acl_item: ACL item, e.g. ``'alice=arwdDxt/bob'``
type: Optional. If passed, all privileges may be reduced to ``['ALL']``.
subname: Optional, e.g. for column privileges. Must be output from
:func:`psycopg2.extensions.quote_ident` or similar.
:meth:`psycopg.sql.Identifier` or similar.
.. warning::
If the ``privs`` or ``privswgo`` attributes of the returned object will
be used to construct an SQL statement, `subname` **must be a valid
identifier** (e.g. by calling :func:`psycopg2.extensions.quote_ident`)
in order to prevent SQL injection attacks.
identifier** (e.g. by calling :meth:`~psycopg.sql.Composable.as_string`
on :class:`psycopg.sql.Identifier`) in order to prevent SQL injection
attacks.
:func:`~pg_grant.sql.grant` and :func:`~pg_grant.sql.revoke` are not
vulnerable, because those functions quote the embedded identifier:
Expand All @@ -141,10 +142,10 @@ def parse_acl_item(
.. code-block:: pycon
>>> import psycopg2
>>> from psycopg2.extensions import quote_ident
>>> conn = psycopg2.connect(...)
>>> parse_acl_item("alice=r/bob", subname=quote_ident("user", conn))
>>> import psycopg
>>> from psycopg.sql import Identifier
>>> conn = psycopg.connect(...)
>>> parse_acl_item("alice=r/bob", subname=Identifier("user").as_string(conn))
>>> privs.privs
['SELECT ("user")']
Expand All @@ -167,7 +168,7 @@ def parse_acl_item(

priv = acl_item[eq_pos + 1 : slash_pos]

def convert_priv(code, keyword):
def convert_priv(code: str, keyword: str) -> None:
nonlocal all_with_grant_option, all_without_grant_option

pos = priv.find(code)
Expand Down
Empty file added src/pg_grant/py.typed
Empty file.
Loading

0 comments on commit 9f715dd

Please sign in to comment.