or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

connection-management.mdcustom-types.mdexception-handling.mdindex.mdlegacy-dbapi.mdnative-interface.mdpostgresql-types.md

custom-types.mddocs/

0

# Custom Types

1

2

Custom Python classes for PostgreSQL-specific data types including intervals and ranges that provide rich functionality beyond basic Python types.

3

4

## Capabilities

5

6

### PGInterval Class

7

8

Python representation of PostgreSQL interval data type with support for multiple input formats and conversions.

9

10

```python { .api }

11

class PGInterval:

12

"""

13

PostgreSQL interval type with comprehensive time component support.

14

15

Properties:

16

- millennia: int - Number of millennia

17

- centuries: int - Number of centuries

18

- decades: int - Number of decades

19

- years: int - Number of years

20

- months: int - Number of months

21

- weeks: int - Number of weeks

22

- days: int - Number of days

23

- hours: int - Number of hours

24

- minutes: int - Number of minutes

25

- seconds: int - Number of seconds

26

- microseconds: int - Number of microseconds

27

"""

28

29

def __init__(

30

self,

31

millennia: int = 0,

32

centuries: int = 0,

33

decades: int = 0,

34

years: int = 0,

35

months: int = 0,

36

weeks: int = 0,

37

days: int = 0,

38

hours: int = 0,

39

minutes: int = 0,

40

seconds: int = 0,

41

microseconds: int = 0

42

):

43

"""

44

Create PGInterval with specified time components.

45

46

Parameters:

47

- millennia: Number of millennia

48

- centuries: Number of centuries

49

- decades: Number of decades

50

- years: Number of years

51

- months: Number of months

52

- weeks: Number of weeks

53

- days: Number of days

54

- hours: Number of hours

55

- minutes: Number of minutes

56

- seconds: Number of seconds

57

- microseconds: Number of microseconds

58

"""

59

60

@classmethod

61

def from_str(cls, interval_str: str) -> PGInterval:

62

"""

63

Parse interval from string representation.

64

65

Parameters:

66

- interval_str: String representation of interval

67

68

Returns:

69

PGInterval object parsed from string

70

71

Raises:

72

ValueError: If string format is invalid

73

"""

74

75

@classmethod

76

def from_str_iso_8601(cls, interval_str: str) -> PGInterval:

77

"""

78

Parse interval from ISO 8601 duration format.

79

80

Parameters:

81

- interval_str: ISO 8601 duration string (e.g., "P1Y2M3DT4H5M6S")

82

83

Returns:

84

PGInterval object parsed from ISO 8601 format

85

86

Raises:

87

ValueError: If ISO 8601 format is invalid

88

"""

89

90

@classmethod

91

def from_str_postgres(cls, interval_str: str) -> PGInterval:

92

"""

93

Parse interval from PostgreSQL native format.

94

95

Parameters:

96

- interval_str: PostgreSQL interval string (e.g., "1 year 2 months")

97

98

Returns:

99

PGInterval object parsed from PostgreSQL format

100

101

Raises:

102

ValueError: If PostgreSQL format is invalid

103

"""

104

105

@classmethod

106

def from_str_sql_standard(cls, interval_str: str) -> PGInterval:

107

"""

108

Parse interval from SQL standard format.

109

110

Parameters:

111

- interval_str: SQL standard interval string

112

113

Returns:

114

PGInterval object parsed from SQL standard format

115

116

Raises:

117

ValueError: If SQL standard format is invalid

118

"""

119

120

def normalize(self) -> PGInterval:

121

"""

122

Normalize interval to standard units.

123

124

Converts larger units to smaller units where possible

125

(e.g., 24 hours to 1 day).

126

127

Returns:

128

New normalized PGInterval object

129

"""

130

131

def to_timedelta(self) -> datetime.timedelta:

132

"""

133

Convert to Python timedelta object.

134

135

Note: Only day, hour, minute, second, and microsecond components

136

are included as timedelta doesn't support months/years.

137

138

Returns:

139

Python timedelta object with convertible components

140

141

Raises:

142

ValueError: If interval contains non-convertible components

143

"""

144

145

def __str__(self) -> str:

146

"""

147

String representation of interval.

148

149

Returns:

150

Human-readable interval string

151

"""

152

153

def __repr__(self) -> str:

154

"""

155

Developer representation of interval.

156

157

Returns:

158

Detailed interval representation

159

"""

160

161

def __eq__(self, other: object) -> bool:

162

"""

163

Test equality with another PGInterval.

164

165

Parameters:

166

- other: Object to compare with

167

168

Returns:

169

True if intervals are equal

170

"""

171

```

172

173

### Range Class

174

175

Python representation of PostgreSQL range data types with support for inclusive/exclusive bounds.

176

177

```python { .api }

178

class Range:

179

"""

180

PostgreSQL range type supporting various boundary conditions.

181

182

Properties:

183

- lower: object - Lower bound value (None for unbounded)

184

- upper: object - Upper bound value (None for unbounded)

185

- bounds: str - Boundary inclusion string ("[)", "[]", "(]", "()")

186

- is_empty: bool - Whether range is empty

187

"""

188

189

def __init__(

190

self,

191

lower: object = None,

192

upper: object = None,

193

bounds: str = "[)",

194

is_empty: bool = False

195

):

196

"""

197

Create range with specified bounds and inclusivity.

198

199

Parameters:

200

- lower: Lower bound value (None for unbounded)

201

- upper: Upper bound value (None for unbounded)

202

- bounds: Boundary specification string:

203

- "[)" : Lower inclusive, upper exclusive (default)

204

- "[]" : Both bounds inclusive

205

- "(]" : Lower exclusive, upper inclusive

206

- "()" : Both bounds exclusive

207

- is_empty: Whether to create an empty range

208

209

Raises:

210

ValueError: If bounds format is invalid

211

"""

212

213

@property

214

def lower_inc(self) -> bool:

215

"""

216

Whether lower bound is inclusive.

217

218

Returns:

219

True if lower bound is inclusive

220

"""

221

222

@property

223

def upper_inc(self) -> bool:

224

"""

225

Whether upper bound is inclusive.

226

227

Returns:

228

True if upper bound is inclusive

229

"""

230

231

@property

232

def lower_inf(self) -> bool:

233

"""

234

Whether lower bound is infinite (unbounded).

235

236

Returns:

237

True if lower bound is infinite

238

"""

239

240

@property

241

def upper_inf(self) -> bool:

242

"""

243

Whether upper bound is infinite (unbounded).

244

245

Returns:

246

True if upper bound is infinite

247

"""

248

249

def __contains__(self, value: object) -> bool:

250

"""

251

Test if value is contained in range.

252

253

Parameters:

254

- value: Value to test for containment

255

256

Returns:

257

True if value is within range bounds

258

"""

259

260

def __str__(self) -> str:

261

"""

262

String representation of range.

263

264

Returns:

265

Range string in PostgreSQL format

266

"""

267

268

def __repr__(self) -> str:

269

"""

270

Developer representation of range.

271

272

Returns:

273

Detailed range representation

274

"""

275

276

def __eq__(self, other: object) -> bool:

277

"""

278

Test equality with another Range.

279

280

Parameters:

281

- other: Object to compare with

282

283

Returns:

284

True if ranges are equal

285

"""

286

```

287

288

### Usage Examples

289

290

#### Working with PGInterval

291

292

```python

293

import pg8000

294

from pg8000 import PGInterval

295

import datetime

296

297

# Create intervals using different methods

298

interval1 = PGInterval(years=1, months=6, days=15, hours=3, minutes=30)

299

interval2 = PGInterval.from_str_iso_8601("P1Y6M15DT3H30M")

300

interval3 = PGInterval.from_str_postgres("1 year 6 months 15 days 03:30:00")

301

302

print(f"Interval 1: {interval1}")

303

print(f"Interval 2: {interval2}")

304

print(f"Interval 3: {interval3}")

305

306

# Normalize interval

307

normalized = interval1.normalize()

308

print(f"Normalized: {normalized}")

309

310

# Convert to timedelta (only day/time components)

311

try:

312

td = PGInterval(days=5, hours=3, minutes=30).to_timedelta()

313

print(f"As timedelta: {td}")

314

except ValueError as e:

315

print(f"Cannot convert: {e}")

316

317

# Use with database

318

conn = pg8000.connect(user="myuser", password="mypass", database="mydb")

319

cursor = conn.cursor()

320

321

cursor.execute("""

322

INSERT INTO events (name, duration)

323

VALUES (%s, %s)

324

""", ("Long Process", interval1))

325

326

cursor.execute("SELECT name, duration FROM events WHERE id = %s", (1,))

327

row = cursor.fetchone()

328

print(f"Event: {row[0]}, Duration: {row[1]}")

329

330

cursor.close()

331

conn.close()

332

```

333

334

#### Working with Range Types

335

336

```python

337

import pg8000

338

from pg8000 import Range

339

import datetime

340

341

# Create different types of ranges

342

int_range = Range(lower=10, upper=20, bounds="[)") # [10, 20)

343

date_range = Range(

344

lower=datetime.date(2023, 1, 1),

345

upper=datetime.date(2023, 12, 31),

346

bounds="[]" # [2023-01-01, 2023-12-31]

347

)

348

unbounded_range = Range(lower=100, upper=None, bounds="[)") # [100, ∞)

349

empty_range = Range(is_empty=True)

350

351

print(f"Integer range: {int_range}")

352

print(f"Date range: {date_range}")

353

print(f"Unbounded range: {unbounded_range}")

354

print(f"Empty range: {empty_range}")

355

356

# Test containment

357

print(f"15 in int_range: {15 in int_range}") # True

358

print(f"20 in int_range: {20 in int_range}") # False (exclusive upper)

359

print(f"5 in int_range: {5 in int_range}") # False

360

361

# Check boundary properties

362

print(f"Lower inclusive: {int_range.lower_inc}") # True

363

print(f"Upper inclusive: {int_range.upper_inc}") # False

364

print(f"Upper infinite: {unbounded_range.upper_inf}") # True

365

366

# Use with database

367

conn = pg8000.connect(user="myuser", password="mypass", database="mydb")

368

cursor = conn.cursor()

369

370

cursor.execute("""

371

CREATE TABLE IF NOT EXISTS price_ranges (

372

id SERIAL PRIMARY KEY,

373

category TEXT,

374

price_range int4range

375

)

376

""")

377

378

cursor.execute("""

379

INSERT INTO price_ranges (category, price_range)

380

VALUES (%s, %s)

381

""", ("Budget", int_range))

382

383

cursor.execute("""

384

SELECT category, price_range

385

FROM price_ranges

386

WHERE price_range @> %s

387

""", (15,)) # Find ranges containing value 15

388

389

row = cursor.fetchone()

390

if row:

391

print(f"Category: {row[0]}, Range: {row[1]}")

392

393

cursor.close()

394

conn.close()

395

```

396

397

#### Advanced Range Operations

398

399

```python

400

import pg8000

401

from pg8000 import Range

402

import datetime

403

404

conn = pg8000.connect(user="myuser", password="mypass", database="mydb")

405

cursor = conn.cursor()

406

407

# Create table with different range types

408

cursor.execute("""

409

CREATE TABLE IF NOT EXISTS bookings (

410

id SERIAL PRIMARY KEY,

411

room_number INT,

412

date_range daterange,

413

time_range tsrange,

414

price_range numrange

415

)

416

""")

417

418

# Insert booking with multiple range types

419

date_range = Range(

420

lower=datetime.date(2023, 12, 20),

421

upper=datetime.date(2023, 12, 25),

422

bounds="[]"

423

)

424

425

time_range = Range(

426

lower=datetime.datetime(2023, 12, 20, 14, 0),

427

upper=datetime.datetime(2023, 12, 25, 12, 0),

428

bounds="[)"

429

)

430

431

price_range = Range(lower=100.00, upper=500.00, bounds="[]")

432

433

cursor.execute("""

434

INSERT INTO bookings (room_number, date_range, time_range, price_range)

435

VALUES (%s, %s, %s, %s)

436

""", (101, date_range, time_range, price_range))

437

438

# Query overlapping ranges

439

cursor.execute("""

440

SELECT room_number, date_range

441

FROM bookings

442

WHERE date_range && %s

443

""", (Range(

444

lower=datetime.date(2023, 12, 22),

445

upper=datetime.date(2023, 12, 28),

446

bounds="[]"

447

),))

448

449

for row in cursor.fetchall():

450

print(f"Room {row[0]} booked during: {row[1]}")

451

452

cursor.close()

453

conn.close()

454

```