macss-modular-api-sqlserver 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (13) hide show
  1. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/PKG-INFO +5 -3
  2. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/README.md +4 -2
  3. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/pyproject.toml +1 -1
  4. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/macss_modular_api_sqlserver.egg-info/PKG-INFO +5 -3
  5. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/modular_api_sqlserver/__init__.py +6 -0
  6. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/modular_api_sqlserver/db_client.py +42 -0
  7. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/tests/test_db_client.py +98 -1
  8. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/tests/test_db_conformance.py +52 -2
  9. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/setup.cfg +0 -0
  10. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/macss_modular_api_sqlserver.egg-info/SOURCES.txt +0 -0
  11. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/macss_modular_api_sqlserver.egg-info/dependency_links.txt +0 -0
  12. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/macss_modular_api_sqlserver.egg-info/requires.txt +0 -0
  13. {macss_modular_api_sqlserver-0.5.0 → macss_modular_api_sqlserver-0.6.0}/src/macss_modular_api_sqlserver.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macss-modular-api-sqlserver
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Official MACSS SQL Server integration package for Python.
5
5
  Author: ccisne.dev
6
6
  License-Expression: MIT
@@ -56,10 +56,12 @@ else:
56
56
 
57
57
  See [example/example.py](example/example.py) for a complete in-memory wiring sample.
58
58
 
59
- ## Current slice
59
+ ## What this package provides
60
60
 
61
61
  - normalized SQL Server connection defaults and redacted summaries
62
62
  - engine-agnostic `DbClient`, `DbRepository`, and transaction contracts
63
63
  - explicit lease ownership semantics for package-owned and application-owned sessions
64
64
  - health contributor and GraphQL support bundle for higher-level integrations
65
- - real driver bindings intentionally remain outside this first slice
65
+ - the application supplies the driver binding (adapter) for its chosen engine and driver
66
+
67
+ This package is **contracts-only by design** and will never ship a driver binding; you choose your engine and driver and provide the adapter. See [ADR-0004](../../../docs/adr/0004-contracts-only-database-packages.md).
@@ -32,10 +32,12 @@ else:
32
32
 
33
33
  See [example/example.py](example/example.py) for a complete in-memory wiring sample.
34
34
 
35
- ## Current slice
35
+ ## What this package provides
36
36
 
37
37
  - normalized SQL Server connection defaults and redacted summaries
38
38
  - engine-agnostic `DbClient`, `DbRepository`, and transaction contracts
39
39
  - explicit lease ownership semantics for package-owned and application-owned sessions
40
40
  - health contributor and GraphQL support bundle for higher-level integrations
41
- - real driver bindings intentionally remain outside this first slice
41
+ - the application supplies the driver binding (adapter) for its chosen engine and driver
42
+
43
+ This package is **contracts-only by design** and will never ship a driver binding; you choose your engine and driver and provide the adapter. See [ADR-0004](../../../docs/adr/0004-contracts-only-database-packages.md).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "macss-modular-api-sqlserver"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "Official MACSS SQL Server integration package for Python."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macss-modular-api-sqlserver
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Official MACSS SQL Server integration package for Python.
5
5
  Author: ccisne.dev
6
6
  License-Expression: MIT
@@ -56,10 +56,12 @@ else:
56
56
 
57
57
  See [example/example.py](example/example.py) for a complete in-memory wiring sample.
58
58
 
59
- ## Current slice
59
+ ## What this package provides
60
60
 
61
61
  - normalized SQL Server connection defaults and redacted summaries
62
62
  - engine-agnostic `DbClient`, `DbRepository`, and transaction contracts
63
63
  - explicit lease ownership semantics for package-owned and application-owned sessions
64
64
  - health contributor and GraphQL support bundle for higher-level integrations
65
- - real driver bindings intentionally remain outside this first slice
65
+ - the application supplies the driver binding (adapter) for its chosen engine and driver
66
+
67
+ This package is **contracts-only by design** and will never ship a driver binding; you choose your engine and driver and provide the adapter. See [ADR-0004](../../../docs/adr/0004-contracts-only-database-packages.md).
@@ -14,6 +14,9 @@ from .db_client import (
14
14
  DbHealthContributor,
15
15
  DbHealthReport,
16
16
  DbHealthStatus,
17
+ DbParameter,
18
+ DbParameterDirection,
19
+ DbProcedureOutcome,
17
20
  DbProviderDescription,
18
21
  DbRepository,
19
22
  DbRepositoryContext,
@@ -40,6 +43,9 @@ __all__ = [
40
43
  "DbHealthContributor",
41
44
  "DbHealthReport",
42
45
  "DbHealthStatus",
46
+ "DbParameter",
47
+ "DbParameterDirection",
48
+ "DbProcedureOutcome",
43
49
  "DbProviderDescription",
44
50
  "DbRepository",
45
51
  "DbRepositoryContext",
@@ -17,6 +17,13 @@ class DbCommandKind(str, Enum):
17
17
  EXECUTE = "execute"
18
18
  BATCH = "batch"
19
19
  SCALAR = "scalar"
20
+ PROCEDURE = "procedure"
21
+
22
+
23
+ class DbParameterDirection(str, Enum):
24
+ INPUT = "input"
25
+ OUTPUT = "output"
26
+ INPUT_OUTPUT = "inputOutput"
20
27
 
21
28
 
22
29
  class DbFailureKind(str, Enum):
@@ -84,6 +91,39 @@ class DbConnectionSettings:
84
91
  )
85
92
 
86
93
 
94
+ @dataclass(frozen=True, slots=True)
95
+ class DbParameter:
96
+ name: str
97
+ value: object | None = None
98
+ direction: DbParameterDirection = DbParameterDirection.INPUT
99
+ type_hint: str | None = None
100
+
101
+ @classmethod
102
+ def input(cls, name: str, value: object | None, type_hint: str | None = None) -> DbParameter:
103
+ return cls(name=name, value=value, direction=DbParameterDirection.INPUT, type_hint=type_hint)
104
+
105
+ @classmethod
106
+ def output(cls, name: str, type_hint: str | None = None) -> DbParameter:
107
+ return cls(name=name, direction=DbParameterDirection.OUTPUT, type_hint=type_hint)
108
+
109
+ @classmethod
110
+ def input_output(
111
+ cls, name: str, value: object | None, type_hint: str | None = None
112
+ ) -> DbParameter:
113
+ return cls(
114
+ name=name,
115
+ value=value,
116
+ direction=DbParameterDirection.INPUT_OUTPUT,
117
+ type_hint=type_hint,
118
+ )
119
+
120
+
121
+ @dataclass(frozen=True, slots=True)
122
+ class DbProcedureOutcome:
123
+ return_value: object | None = None
124
+ output_parameters: Mapping[str, object] | None = None
125
+
126
+
87
127
  @dataclass(frozen=True, slots=True)
88
128
  class DbCommand:
89
129
  kind: DbCommandKind
@@ -104,12 +144,14 @@ class DbExecutionMetadata:
104
144
  class DbRowSet:
105
145
  rows: list[Mapping[str, object]]
106
146
  metadata: DbExecutionMetadata
147
+ procedure: DbProcedureOutcome | None = None
107
148
 
108
149
 
109
150
  @dataclass(frozen=True, slots=True)
110
151
  class DbExecutionSummary:
111
152
  affected_count: int
112
153
  metadata: DbExecutionMetadata
154
+ procedure: DbProcedureOutcome | None = None
113
155
 
114
156
 
115
157
  @dataclass(frozen=True, slots=True)
@@ -16,6 +16,9 @@ from modular_api_sqlserver import (
16
16
  DbGraphqlSupport,
17
17
  DbHealthContributor,
18
18
  DbHealthStatus,
19
+ DbParameter,
20
+ DbParameterDirection,
21
+ DbProcedureOutcome,
19
22
  DbProviderDescription,
20
23
  DbRepository,
21
24
  DbRepositoryContext,
@@ -473,4 +476,98 @@ class _UserStatsRepository(DbRepository[str]):
473
476
  label="users.count",
474
477
  )
475
478
  )
476
- return result.map(lambda value: int(value.value))
479
+ return result.map(lambda value: int(value.value))
480
+
481
+
482
+ # --- 0.6.0: typed parameters and stored-procedure support ---
483
+
484
+
485
+ def test_db_parameter_input_captures_name_value_and_optional_type_hint() -> None:
486
+ plain = DbParameter.input("id", 42)
487
+ assert plain.name == "id"
488
+ assert plain.value == 42
489
+ assert plain.direction is DbParameterDirection.INPUT
490
+ assert plain.type_hint is None
491
+
492
+ hinted = DbParameter.input("picture", b"\x01\x02\x03", "varbinary(max)")
493
+ assert hinted.direction is DbParameterDirection.INPUT
494
+ assert hinted.type_hint == "varbinary(max)"
495
+
496
+
497
+ def test_db_parameter_output_carries_no_input_value_and_defaults_direction() -> None:
498
+ out = DbParameter.output("total", "int")
499
+ assert out.name == "total"
500
+ assert out.value is None
501
+ assert out.direction is DbParameterDirection.OUTPUT
502
+ assert out.type_hint == "int"
503
+
504
+
505
+ def test_db_parameter_input_output_marks_bidirectional_parameters() -> None:
506
+ io = DbParameter.input_output("counter", 1, "int")
507
+ assert io.direction is DbParameterDirection.INPUT_OUTPUT
508
+ assert io.value == 1
509
+
510
+
511
+ def test_db_parameter_defaults_direction_to_input_when_constructed_directly() -> None:
512
+ param = DbParameter(name="name", value="foto.jpg")
513
+ assert param.direction is DbParameterDirection.INPUT
514
+
515
+
516
+ def test_db_parameter_flows_through_db_command_parameters_unchanged() -> None:
517
+ command = DbCommand(
518
+ kind=DbCommandKind.PROCEDURE,
519
+ text="spEliminarFoto",
520
+ parameters=(DbParameter.input("nombre", "foto.jpg"), "positional-still-allowed"),
521
+ )
522
+ assert len(command.parameters) == 2
523
+ assert isinstance(command.parameters[0], DbParameter)
524
+ assert command.parameters[0].name == "nombre"
525
+ assert command.parameters[1] == "positional-still-allowed"
526
+
527
+
528
+ def test_db_command_kind_exposes_procedure() -> None:
529
+ assert DbCommandKind.PROCEDURE.value == "procedure"
530
+
531
+
532
+ def test_db_procedure_outcome_carries_return_value_and_output_parameters() -> None:
533
+ outcome = DbProcedureOutcome(return_value=0, output_parameters={"total": 5})
534
+ assert outcome.return_value == 0
535
+ assert outcome.output_parameters == {"total": 5}
536
+
537
+
538
+ def test_db_procedure_outcome_allows_both_fields_to_be_absent() -> None:
539
+ empty = DbProcedureOutcome()
540
+ assert empty.return_value is None
541
+ assert empty.output_parameters is None
542
+
543
+
544
+ def test_db_procedure_outcome_attaches_optionally_to_db_row_set() -> None:
545
+ without_outcome = DbRowSet(
546
+ rows=[{"id": 1}],
547
+ metadata=DbExecutionMetadata(duration=1),
548
+ )
549
+ assert without_outcome.procedure is None
550
+
551
+ with_outcome = DbRowSet(
552
+ rows=[{"id": 1}],
553
+ metadata=DbExecutionMetadata(duration=1),
554
+ procedure=DbProcedureOutcome(return_value=0),
555
+ )
556
+ assert with_outcome.procedure is not None
557
+ assert with_outcome.procedure.return_value == 0
558
+
559
+
560
+ def test_db_procedure_outcome_attaches_optionally_to_db_execution_summary() -> None:
561
+ without_outcome = DbExecutionSummary(
562
+ affected_count=1,
563
+ metadata=DbExecutionMetadata(duration=1),
564
+ )
565
+ assert without_outcome.procedure is None
566
+
567
+ with_outcome = DbExecutionSummary(
568
+ affected_count=1,
569
+ metadata=DbExecutionMetadata(duration=1),
570
+ procedure=DbProcedureOutcome(output_parameters={"id": 99}),
571
+ )
572
+ assert with_outcome.procedure is not None
573
+ assert with_outcome.procedure.output_parameters == {"id": 99}
@@ -3,7 +3,16 @@ from __future__ import annotations
3
3
  import json
4
4
  from pathlib import Path
5
5
 
6
- from modular_api_sqlserver import DbConnectionSettings, DbFailure, DbFailureKind, DbResult
6
+ from modular_api_sqlserver import (
7
+ DbCommand,
8
+ DbCommandKind,
9
+ DbConnectionSettings,
10
+ DbFailure,
11
+ DbFailureKind,
12
+ DbParameter,
13
+ DbProcedureOutcome,
14
+ DbResult,
15
+ )
7
16
 
8
17
 
9
18
  def _load_fixture() -> dict[str, object]:
@@ -65,4 +74,45 @@ def test_matches_the_shared_db_result_fixture() -> None:
65
74
  )
66
75
  )
67
76
 
68
- assert mapped_failure.failure.code == result_fixture["wrappedFailureCode"]
77
+ assert mapped_failure.failure.code == result_fixture["wrappedFailureCode"]
78
+
79
+
80
+ def test_matches_the_shared_typed_parameter_and_stored_procedure_fixture() -> None:
81
+ command_fixture = _load_fixture()["command"]
82
+ input_fixture = command_fixture["inputParameter"]
83
+
84
+ input_parameter = DbParameter.input(
85
+ input_fixture["name"], input_fixture["value"], input_fixture["typeHint"]
86
+ )
87
+ assert input_parameter.name == input_fixture["name"]
88
+ assert input_parameter.value == input_fixture["value"]
89
+ assert input_parameter.type_hint == input_fixture["typeHint"]
90
+ assert input_parameter.direction.value == input_fixture["expectedDirection"]
91
+
92
+ output_fixture = command_fixture["outputParameter"]
93
+ output_parameter = DbParameter.output(output_fixture["name"], output_fixture["typeHint"])
94
+ assert output_parameter.value is None
95
+ assert output_parameter.direction.value == output_fixture["expectedDirection"]
96
+
97
+ command = DbCommand(
98
+ kind=DbCommandKind.PROCEDURE,
99
+ text=command_fixture["procedureName"],
100
+ parameters=(input_parameter,),
101
+ )
102
+ assert command.kind.value == "procedure"
103
+ assert command.text == command_fixture["procedureName"]
104
+ assert isinstance(command.parameters[0], DbParameter)
105
+
106
+ outcome_fixture = command_fixture["outcome"]
107
+ outcome = DbProcedureOutcome(
108
+ return_value=outcome_fixture["returnValue"],
109
+ output_parameters={
110
+ outcome_fixture["outputParameterName"]: outcome_fixture["outputParameterValue"]
111
+ },
112
+ )
113
+ assert outcome.return_value == outcome_fixture["returnValue"]
114
+ assert outcome.output_parameters is not None
115
+ assert (
116
+ outcome.output_parameters[outcome_fixture["outputParameterName"]]
117
+ == outcome_fixture["outputParameterValue"]
118
+ )