Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/workflows/type-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: Type check
on: # yamllint disable-line rule:truthy
workflow_dispatch:
push:
branches:
- master
- v*
pull_request:
branches:
- master
- v*

# ty runs here rather than on pre-commit.ci, which blocks the network access ty
# needs to resolve this project's dependencies.
jobs:
ty:
name: ty
runs-on: ubuntu-latest
permissions:
contents: read
steps:

- uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
python-version: '3.11'

- name: Run ty
run: uv run --frozen ty check
39 changes: 21 additions & 18 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
default_language_version:
python: python3.11
ci:
# The ty hook shells out to uv to resolve this project's dependencies, and
# pre-commit.ci runs hooks without network access. The Type check workflow
# runs ty in GitHub Actions instead.
skip:
- ty
repos:
- repo: 'https://github.com/pre-commit/pre-commit-hooks'
rev: v6.0.0
Expand Down Expand Up @@ -45,22 +51,19 @@ repos:
args:
- '--check'
- '--diff'
- repo: 'https://github.com/pre-commit/mirrors-mypy'
rev: v2.1.0
# Runs the ty pinned in pyproject.toml, the same command the Type check
# workflow runs, so the version is declared in exactly one place. The hook
# takes no file arguments: an edit in one file can move diagnostics into
# another, so ty rechecks its whole configured scope every run.
- repo: local
hooks:
- id: mypy
args:
- '--show-error-codes'
- '--ignore-missing-imports'
- '--explicit-package-bases'
files: '^dbt/adapters'
- id: mypy
alias: mypy-check
stages:
- manual
args:
- '--show-error-codes'
- '--pretty'
- '--ignore-missing-imports'
- '--explicit-package-bases'
files: '^dbt/adapters'
- id: ty
name: ty
entry: uv run --frozen ty check
language: system
types_or:
- python
- pyi
pass_filenames: false
always_run: true
require_serial: true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
- **Behavior change (generated SQL only):** the few macros that hand-formatted `[bracket]` identifiers now use `adapter.quote()`, matching the `"double quoted"` identifiers `{{ relation }}` already rendered, so one statement no longer mixes both styles. Affects `USE` (now via the existing `get_use_database_sql()` helper), `CREATE SCHEMA`, contract column lists, grantees, index/constraint names and generated test view names. Server-side `QUOTENAME()` keeps brackets by design. Visible only if you parse dbt's generated SQL or have custom macros assuming brackets. [#785](https://github.com/dbt-msft/dbt-sqlserver/issues/785)
- Identifier quoting escapes an embedded `"` by doubling it (`ab"cd` → `"ab""cd"`), in both `adapter.quote()` and relation rendering (`SQLServerRelation.quoted`). Needed twice over: a `"` requires no escaping inside `[brackets]` but does inside double quotes, so the delimiter change above would otherwise have rejected names the adapter previously accepted; and the string-literal fix above renders identifiers through the relation, so a schema containing a `"` depends on it. [#785](https://github.com/dbt-msft/dbt-sqlserver/issues/785)

#### Under the hood

- Replace mypy with [`ty`](https://docs.astral.sh/ty/) for type checking, completing the move to a single Astral toolchain (Ruff for lint and format, `ty` for types). Same scope as before (`dbt/adapters`, unresolvable `dbt.*` imports ignored), configured in `pyproject.toml`. `ty` runs in a new Type check workflow rather than on pre-commit.ci, which blocks the network access it needs. [#712](https://github.com/dbt-msft/dbt-sqlserver/issues/712)

### v1.11.0

#### Features
Expand Down
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ dev: ## Installs adapter in develop mode along with development dependencies
@\
uv sync --all-extras && uv run pre-commit install

.PHONY: mypy
mypy: ## Runs mypy against staged changes for static type checking.
.PHONY: ty
ty: ## Runs ty over the adapter package for static type checking.
@\
uv run pre-commit run --hook-stage manual mypy-check
uv run pre-commit run --hook-stage manual ty

.PHONY: ruff
ruff: ## Runs ruff against staged changes to enforce style guide.
Expand All @@ -22,10 +22,10 @@ format: ## Runs ruff format against staged changes to enforce style guide.
uv run pre-commit run --hook-stage manual ruff-format-check -v

.PHONY: lint
lint: ## Runs ruff and mypy code checks against staged changes.
lint: ## Runs ruff and ty code checks against staged changes.
@status=0; \
uv run pre-commit run ruff-check-manual --hook-stage manual || status=1; \
uv run pre-commit run mypy-check --hook-stage manual || status=1; \
uv run pre-commit run ty --hook-stage manual || status=1; \
exit $$status

.PHONY: all
Expand All @@ -49,7 +49,7 @@ test: ## Runs unit tests and code checks against staged changes.
uv run pytest -n auto -ra -v tests/unit || status=1; \
uv run pre-commit run ruff-format-check --hook-stage manual || status=1; \
uv run pre-commit run ruff-check-manual --hook-stage manual || status=1; \
uv run pre-commit run mypy-check --hook-stage manual || status=1; \
uv run pre-commit run ty --hook-stage manual || status=1; \
exit $$status

.PHONY: server
Expand Down
10 changes: 8 additions & 2 deletions dbt/adapters/sqlserver/relation_configs/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,13 +593,19 @@ def index_config_changes(
SQLServerIndexConfig.parse_relation_results(rows_by_name[name])
)
changes.append(
SQLServerIndexConfigChange(action=RelationConfigChangeAction.drop, context=context)
SQLServerIndexConfigChange(
# `action` is inherited from RelationConfigChange, a dataclass a
# type checker cannot see; see [tool.ty.rules] in pyproject.toml.
action=RelationConfigChangeAction.drop, # ty: ignore[unknown-argument]
context=context,
)
)
for name, config in expected_by_name.items():
if name not in existing_names:
changes.append(
SQLServerIndexConfigChange(
action=RelationConfigChangeAction.create, context=config
action=RelationConfigChangeAction.create, # ty: ignore[unknown-argument]
context=config,
)
)
return changes, warnings
21 changes: 13 additions & 8 deletions dbt/adapters/sqlserver/sqlserver_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime as _dt
import re
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple, Type, Union

import agate
import dbt_common.exceptions
Expand Down Expand Up @@ -187,7 +187,8 @@ class SQLServerAdapter(SQLAdapter):
"""

ConnectionManager = SQLServerConnectionManager
Column = SQLServerColumn
# Annotated because __init__ swaps in the SQLServerColumnNative subclass.
Column: Type[SQLServerColumn] = SQLServerColumn
AdapterSpecificConfigs = SQLServerConfigs
Relation = SQLServerRelation

Expand Down Expand Up @@ -220,8 +221,12 @@ def __init__(self, config, mp_context=None):

@property
def _behavior_flags(self) -> List[BehaviorFlag]:
return [
{
# dbt-common declares BehaviorFlag's optional keys with a NotRequired
# that falls back to Optional under a try/except ImportError shim, so a
# type checker reads `source` and `docs_url` as required and rejects
# every flag below. The suppressions go stale once that shim is dropped.
return [ # ty: ignore[invalid-return-type]
{ # ty: ignore[missing-typed-dict-key]
"name": "dbt_sqlserver_use_default_schema_concat",
"default": False,
"description": (
Expand All @@ -233,7 +238,7 @@ def _behavior_flags(self) -> List[BehaviorFlag]:
"macro in your project instead."
),
},
{
{ # ty: ignore[missing-typed-dict-key]
"name": "dbt_sqlserver_disable_empty_relation_aliases",
"default": True,
"description": (
Expand All @@ -242,7 +247,7 @@ def _behavior_flags(self) -> List[BehaviorFlag]:
"out of alias generation temporarily for testing."
),
},
{
{ # ty: ignore[missing-typed-dict-key]
"name": "dbt_sqlserver_use_native_string_types",
"default": True,
"description": (
Expand All @@ -254,7 +259,7 @@ def _behavior_flags(self) -> List[BehaviorFlag]:
"and will be removed in a future release."
),
},
{
{ # ty: ignore[missing-typed-dict-key]
"name": "dbt_sqlserver_enable_safe_type_expansion",
"default": False,
"description": (
Expand All @@ -264,7 +269,7 @@ def _behavior_flags(self) -> List[BehaviorFlag]:
"and numeric(p,s) -> numeric(p2,s2) using alter column."
),
},
{
{ # ty: ignore[missing-typed-dict-key]
"name": "dbt_sqlserver_use_dbt_transactions",
"default": True,
"description": (
Expand Down
2 changes: 1 addition & 1 deletion dbt/adapters/sqlserver/sqlserver_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

@dataclass(frozen=True, eq=False, repr=False)
class SQLServerRelation(BaseRelation):
type: Optional[SQLServerRelationType] = None # type: ignore
type: Optional[SQLServerRelationType] = None
include_policy: SQLServerIncludePolicy = field(
default_factory=lambda: SQLServerIncludePolicy()
)
Expand Down
20 changes: 18 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ dev = [
"flaky",
"freezegun>=1.5.0,<2.0",
"ipdb",
"mypy==2.1.0",
"pre-commit",
"pytest",
"pytest-cov",
Expand All @@ -77,6 +76,9 @@ dev = [
"ruff",
"tox>=3.13",
"twine",
# Pinned exactly: this is the single source of the ty version, read by the
# pre-commit hook, `make ty` and the Type check workflow alike.
"ty==0.0.64",
]

[project.urls]
Expand Down Expand Up @@ -119,4 +121,18 @@ known-first-party = ["dbt"]
ban-relative-imports = "all"

[tool.ty.environment]
python = ".venv"
# Check against the oldest interpreter the adapter is built for, matching
# ruff's target-version, rather than whichever version .venv happens to hold.
python-version = "3.11"

[tool.ty.src]
# The adapter package only; the test suite is not type checked.
include = ["dbt/adapters"]

[tool.ty.rules]
# `dbt` is a namespace split across distributions: dbt-core and dbt-adapters
# own `dbt/` and `dbt/adapters/` in site-packages, this repo contributes
# `dbt/adapters/sqlserver` from the project root, and the two halves are joined
# at runtime by the editable install's import hook. No static checker can
# follow that, so every `dbt.*` import reads as unresolvable here.
unresolved-import = "ignore"
Loading