query-farm-airport-test-server 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,182 @@
1
+ import collections.abc
2
+ from collections.abc import Iterable, Iterator
3
+ from typing import Any, Generic, TypeVar, overload
4
+
5
+ # Type variable for the dictionary's value type
6
+ VT = TypeVar("VT")
7
+
8
+ # Type variable for the value in the fromkeys classmethod
9
+ _VT_FK_OUT = TypeVar("_VT_FK_OUT")
10
+
11
+ # Represents the type of None, for precise typing in fromkeys
12
+ NoneType = type(None)
13
+
14
+
15
+ class CaseInsensitiveDict(collections.abc.MutableMapping[str, VT], Generic[VT]):
16
+ """
17
+ A dictionary that stores string keys in a case-insensitive manner.
18
+
19
+ The original case of the key is preserved upon first insertion or if a key
20
+ is updated with a new casing. All lookups (get, set, delete, in) are
21
+ case-insensitive with respect to string keys.
22
+
23
+ Example:
24
+ cid = CaseInsensitiveDict({'Content-Type': 'application/json'})
25
+ print(cid['content-type']) # Output: application/json
26
+ print('CONTENT-TYPE' in cid) # Output: True
27
+
28
+ cid['content-type'] = 'text/plain' # Updates value and preserved key case
29
+ print(list(cid.keys())) # Output: ['content-type']
30
+ print(cid) # Output: CaseInsensitiveDict({'content-type': 'text/plain'})
31
+ """
32
+
33
+ # Overloads for __init__ to guide type checkers, similar to `dict`
34
+ # @overload
35
+ # def __init__(self, **kwargs: VT) -> None: ...
36
+ # @overload
37
+ # def __init__(self, map: Mapping[str, VT], **kwargs: VT) -> None: ...
38
+ # @overload
39
+ # def __init__(self, iterable: Iterable[Tuple[str, VT]], **kwargs: VT) -> None: ...
40
+
41
+ def __init__(self, *args: Any, **kwargs: VT) -> None:
42
+ """
43
+ Initializes the CaseInsensitiveDict.
44
+ It can be initialized like a standard dict:
45
+ - CaseInsensitiveDict(mapping, **kwargs)
46
+ - CaseInsensitiveDict(iterable, **kwargs)
47
+ - CaseInsensitiveDict(**kwargs)
48
+ """
49
+ self._store: dict[str, VT] = {}
50
+ # _key_map maps lowercase key to the actual cased key used in _store
51
+ self._key_map: dict[str, str] = {}
52
+
53
+ # Populate from args and kwargs using the update method,
54
+ # which in turn uses our __setitem__.
55
+ # dict(*args, **kwargs) creates a standard dictionary from the input,
56
+ # which self.update will then process.
57
+ self.update(dict(*args, **kwargs))
58
+
59
+ def __setitem__(self, key: str, value: VT) -> None:
60
+ """
61
+ Set d[key] to value. Key lookups are case-insensitive.
62
+ The case of the key used in this operation is preserved.
63
+ """
64
+ if not isinstance(key, str):
65
+ raise TypeError(f"Keys must be strings for CaseInsensitiveDict, got {type(key).__name__}")
66
+
67
+ lower_key = key.lower()
68
+
69
+ # If a key with the same lowercase form but different actual casing
70
+ # already exists, we need to remove the old entry from _store
71
+ # because _store's keys are case-sensitive.
72
+ # This ensures the new key's casing is preserved.
73
+ if lower_key in self._key_map:
74
+ original_cased_key = self._key_map[lower_key]
75
+ if original_cased_key != key:
76
+ # The new key's casing is different; remove the old one from _store.
77
+ # The value will be associated with the new key's casing.
78
+ del self._store[original_cased_key]
79
+
80
+ # Store the value with the new key (preserving its case)
81
+ self._store[key] = value
82
+ # Update the map to point to the current key's casing
83
+ self._key_map[lower_key] = key
84
+
85
+ def __getitem__(self, key: str) -> VT:
86
+ """Return the value for key (case-insensitive lookup). Raises KeyError if not found."""
87
+ if not isinstance(key, str):
88
+ raise TypeError(f"Keys must be strings for CaseInsensitiveDict, got {type(key).__name__}")
89
+
90
+ lower_key = key.lower()
91
+ if lower_key not in self._key_map:
92
+ raise KeyError(f"Key not found: '{key}'")
93
+
94
+ # Retrieve using the original (preserved) cased key
95
+ original_cased_key = self._key_map[lower_key]
96
+ return self._store[original_cased_key]
97
+
98
+ def __delitem__(self, key: str) -> None:
99
+ """Delete d[key]. Key lookups are case-insensitive. Raises KeyError if not found."""
100
+ if not isinstance(key, str):
101
+ raise TypeError(f"Keys must be strings for CaseInsensitiveDict, got {type(key).__name__}")
102
+
103
+ lower_key = key.lower()
104
+ if lower_key not in self._key_map:
105
+ raise KeyError(f"Key not found: '{key}'")
106
+
107
+ original_cased_key = self._key_map[lower_key]
108
+ del self._store[original_cased_key]
109
+ del self._key_map[lower_key]
110
+
111
+ def __iter__(self) -> Iterator[str]:
112
+ """Return an iterator over the keys (with preserved casing)."""
113
+ return iter(self._store.keys())
114
+
115
+ def __len__(self) -> int:
116
+ """Return the number of items in the dictionary."""
117
+ return len(self._store)
118
+
119
+ def __repr__(self) -> str:
120
+ """Return a string representation of the dictionary."""
121
+ return f"{type(self).__name__}({self._store})"
122
+
123
+ def __eq__(self, other: object) -> bool:
124
+ """
125
+ Compare this dictionary with another mapping for equality.
126
+ Comparison is case-insensitive for keys.
127
+ Both keys and values must match.
128
+ """
129
+ if not isinstance(other, collections.abc.Mapping):
130
+ return NotImplemented
131
+
132
+ if len(self) != len(other):
133
+ return False
134
+
135
+ # Convert self to a canonical form (all string keys lowercased)
136
+ self_canonical: dict[str, VT] = {k.lower(): v for k, v in self.items()}
137
+
138
+ other_canonical: dict[str, Any] = {}
139
+ try:
140
+ for o_key, o_value in other.items():
141
+ if isinstance(o_key, str):
142
+ other_canonical[o_key.lower()] = o_value
143
+ else:
144
+ # If `other` contains any non-string key, it cannot be
145
+ # equal to this CaseInsensitiveDict, which only supports string keys.
146
+ return False
147
+ except AttributeError:
148
+ # This might happen if o_key.lower() fails unexpectedly,
149
+ # or if other.items() is problematic.
150
+ return NotImplemented
151
+
152
+ return self_canonical == other_canonical
153
+
154
+ def copy(self) -> "CaseInsensitiveDict[VT]":
155
+ """Return a shallow copy of the dictionary."""
156
+ # The __init__ method can conveniently handle another CaseInsensitiveDict
157
+ # or any mapping to create a new instance.
158
+ return type(self)(self)
159
+
160
+ # Overloads for fromkeys to ensure correct return type inference
161
+ @overload
162
+ @classmethod
163
+ def fromkeys(cls, iterable: Iterable[str]) -> "CaseInsensitiveDict[NoneType]": ...
164
+
165
+ @overload
166
+ @classmethod
167
+ def fromkeys(cls, iterable: Iterable[str], value: _VT_FK_OUT) -> "CaseInsensitiveDict[_VT_FK_OUT]": ...
168
+
169
+ @classmethod
170
+ def fromkeys(cls, iterable: Iterable[str], value: Any = None) -> "CaseInsensitiveDict[Any]":
171
+ """
172
+ Create a new dictionary with keys from iterable and values set to value.
173
+ If value is not specified, it defaults to None.
174
+ Keys in the iterable must be strings.
175
+ """
176
+ instance = cls() # Creates an empty CaseInsensitiveDict
177
+ for key in iterable:
178
+ if not isinstance(key, str):
179
+ raise TypeError(f"All keys in iterable for fromkeys must be strings, got {type(key).__name__}")
180
+ # This uses the instance's __setitem__, ensuring case-insensitivity logic
181
+ instance[key] = value
182
+ return instance
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.3
2
+ Name: query-farm-airport-test-server
3
+ Version: 0.1.0
4
+ Summary: An Apache Arrow Flight server that is used to test the Airport extension for DuckDB.
5
+ Project-URL: Repository, https://github.com/query-farm/python-airport-test-server.git
6
+ Project-URL: Issues, https://github.com/query-farm/python-airport-test-server/issues
7
+ Author-email: Rusty Conover <rusty@query.farm>
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: duckdb>=1.3.1
10
+ Requires-Dist: pyarrow>=20.0.0
11
+ Requires-Dist: query-farm-duckdb-json-serialization>=0.1.1
12
+ Requires-Dist: query-farm-flight-server
13
+ Description-Content-Type: text/markdown
14
+
15
+ # query-farm-airport-test-server
16
+
17
+ **`query-farm-airport-test-server`** is a Python module that implements a lightweight in-memory Arrow Flight server for use with the [Airport DuckDB extension](https://airport.query.farm). It showcases nearly all of the Airport extension's capabilities and is designed primarily for testing and CI integration.
18
+
19
+ > ⚠️ This server is not intended as a tutorial or reference for writing Arrow Flight servers from scratch. Its purpose is to comprehensively test feature coverage, and the implementation reflects that complexity.
20
+
21
+ ## Features
22
+
23
+ - In-memory storage — no persistent state
24
+ - Accepts any authentication token
25
+ - Supports full reset of data via client call
26
+ - Ideal for CI pipelines and integration tests
27
+
28
+ ## Installation
29
+
30
+ ```sh
31
+ pip install query-farm-airport-test-server
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```sh
37
+ $ airport_test_server
38
+ ```
39
+
40
+ Once running, the server can be used with the test suite included in the Airport DuckDB extension.
@@ -0,0 +1,9 @@
1
+ query_farm_airport_test_server/__init__.py,sha256=wLtpoyDnOaaaCRT0zVxXu03UlY9g5Bo04RCrmB4wclQ,72
2
+ query_farm_airport_test_server/auth.py,sha256=7ssmh_hN9WKxWwsgJnPgxNK-c_geeLqDYUo-2zHV63o,150
3
+ query_farm_airport_test_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ query_farm_airport_test_server/server.py,sha256=Q-ZCnGqfJVeVHCTEScV-sEj9MFD7VPEUmw5VfzBX5mY,71725
5
+ query_farm_airport_test_server/utils.py,sha256=-Q_jI5VnjEpVy2JUPXVqvD469-xWOpIMO1ioLk5bwbQ,7505
6
+ query_farm_airport_test_server-0.1.0.dist-info/METADATA,sha256=MxgnRGuDhgLH2yO3Y9OL5UdCjbmlr-kDCnoUBcYSsy8,1572
7
+ query_farm_airport_test_server-0.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
8
+ query_farm_airport_test_server-0.1.0.dist-info/entry_points.txt,sha256=T1BH0oQKEPQHMpatcQMSGJnAIXIm6nBs5BRtF6u-R3g,81
9
+ query_farm_airport_test_server-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.26.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ airport_test_server = query_farm_airport_test_server:do_server