sql-blocks 1.25.1901__py3-none-any.whl → 1.25.1021229__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.
sql_blocks/sql_blocks.py CHANGED
@@ -42,23 +42,34 @@ class SQLObject:
42
42
  if not table_name:
43
43
  return
44
44
  cls = SQLObject
45
+ is_file_name = any([
46
+ '/' in table_name, '.' in table_name
47
+ ])
48
+ ref = table_name
49
+ if is_file_name:
50
+ ref = table_name.split('/')[-1].split('.')[0]
45
51
  if cls.ALIAS_FUNC:
46
- self.__alias = cls.ALIAS_FUNC(table_name)
52
+ self.__alias = cls.ALIAS_FUNC(ref)
47
53
  elif ' ' in table_name.strip():
48
54
  table_name, self.__alias = table_name.split()
49
- elif '_' in table_name:
55
+ elif '_' in ref:
50
56
  self.__alias = ''.join(
51
57
  word[0].lower()
52
- for word in table_name.split('_')
58
+ for word in ref.split('_')
53
59
  )
54
60
  else:
55
- self.__alias = table_name.lower()[:3]
61
+ self.__alias = ref.lower()[:3]
56
62
  self.values.setdefault(FROM, []).append(f'{table_name} {self.alias}')
57
63
 
58
64
  @property
59
65
  def table_name(self) -> str:
60
66
  return self.values[FROM][0].split()[0]
61
67
 
68
+ def set_file_format(self, pattern: str):
69
+ if '{' not in pattern:
70
+ pattern = '{}' + pattern
71
+ self.values[FROM][0] = pattern.format(self.aka())
72
+
62
73
  @property
63
74
  def alias(self) -> str:
64
75
  if self.__alias:
@@ -360,15 +371,20 @@ class ExpressionField:
360
371
  class FieldList:
361
372
  separator = ','
362
373
 
363
- def __init__(self, fields: list=[], class_types = [Field]):
374
+ def __init__(self, fields: list=[], class_types = [Field], ziped: bool=False):
364
375
  if isinstance(fields, str):
365
376
  fields = [
366
377
  f.strip() for f in fields.split(self.separator)
367
378
  ]
368
379
  self.fields = fields
369
380
  self.class_types = class_types
381
+ self.ziped = ziped
370
382
 
371
383
  def add(self, name: str, main: SQLObject):
384
+ if self.ziped: # --- One class per field...
385
+ for field, class_type in zip(self.fields, self.class_types):
386
+ class_type.add(field, main)
387
+ return
372
388
  for field in self.fields:
373
389
  for class_type in self.class_types:
374
390
  class_type.add(field, main)
@@ -423,23 +439,23 @@ class Position(Enum):
423
439
  class Where:
424
440
  prefix = ''
425
441
 
426
- def __init__(self, expr: str):
427
- self.expr = expr
442
+ def __init__(self, content: str):
443
+ self.content = content
428
444
 
429
445
  @classmethod
430
446
  def __constructor(cls, operator: str, value):
431
- return cls(expr=f'{operator} {quoted(value)}')
447
+ return cls(f'{operator} {quoted(value)}')
432
448
 
433
449
  @classmethod
434
450
  def eq(cls, value):
435
451
  return cls.__constructor('=', value)
436
452
 
437
453
  @classmethod
438
- def contains(cls, content: str, pos: Position = Position.Middle):
454
+ def contains(cls, text: str, pos: Position = Position.Middle):
439
455
  return cls(
440
456
  "LIKE '{}{}{}'".format(
441
457
  '%' if pos != Position.StartsWith else '',
442
- content,
458
+ text,
443
459
  '%' if pos != Position.EndsWith else ''
444
460
  )
445
461
  )
@@ -470,14 +486,47 @@ class Where:
470
486
  values = ','.join(quoted(v) for v in values)
471
487
  return cls(f'IN ({values})')
472
488
 
489
+ @classmethod
490
+ def formula(cls, formula: str):
491
+ where = cls( ExpressionField(formula) )
492
+ where.add = where.add_expression
493
+ return where
494
+
495
+ def add_expression(self, name: str, main: SQLObject):
496
+ self.content = self.content.format(name, main)
497
+ main.values.setdefault(WHERE, []).append('{} {}'.format(
498
+ self.prefix, self.content
499
+ ))
500
+
501
+ @classmethod
502
+ def join(cls, query: SQLObject):
503
+ where = cls(query)
504
+ where.add = where.add_join
505
+ return where
506
+
507
+ def add_join(self, name: str, main: SQLObject):
508
+ query = self.content
509
+ main.values[FROM].append(f',{query.table_name} {query.alias}')
510
+ for key in USUAL_KEYS:
511
+ main.update_values(key, query.values.get(key, []))
512
+ main.values.setdefault(WHERE, []).append('({a1}.{f1} = {a2}.{f2})'.format(
513
+ a1=main.alias, f1=name,
514
+ a2=query.alias, f2=query.key_field
515
+ ))
516
+
473
517
  def add(self, name: str, main: SQLObject):
474
518
  func_type = FUNCTION_CLASS.get(name.lower())
519
+ exists = any(
520
+ main.is_named_field(fld, SELECT)
521
+ for fld in main.values.get(SELECT, [])
522
+ if name in fld
523
+ )
475
524
  if func_type:
476
525
  name = func_type.format('*', main)
477
- else:
526
+ elif not exists:
478
527
  name = Field.format(name, main)
479
528
  main.values.setdefault(WHERE, []).append('{}{} {}'.format(
480
- self.prefix, name, self.expr
529
+ self.prefix, name, self.content
481
530
  ))
482
531
 
483
532
 
@@ -485,6 +534,10 @@ eq, contains, gt, gte, lt, lte, is_null, inside = (
485
534
  getattr(Where, method) for method in
486
535
  ('eq', 'contains', 'gt', 'gte', 'lt', 'lte', 'is_null', 'inside')
487
536
  )
537
+ startswith, endswith = [
538
+ lambda x: contains(x, Position.StartsWith),
539
+ lambda x: contains(x, Position.EndsWith)
540
+ ]
488
541
 
489
542
 
490
543
  class Not(Where):
@@ -492,7 +545,7 @@ class Not(Where):
492
545
 
493
546
  @classmethod
494
547
  def eq(cls, value):
495
- return Where(expr=f'<> {quoted(value)}')
548
+ return Where(f'<> {quoted(value)}')
496
549
 
497
550
 
498
551
  class Case:
@@ -533,7 +586,7 @@ class Options:
533
586
  child: Where
534
587
  for field, child in self.__children.items():
535
588
  conditions.append(' {} {} '.format(
536
- Field.format(field, main), child.expr
589
+ Field.format(field, main), child.content
537
590
  ))
538
591
  main.values.setdefault(WHERE, []).append(
539
592
  '(' + logical_separator.join(conditions) + ')'
@@ -561,7 +614,7 @@ class Clause:
561
614
  found = re.findall(r'^_\d', name)
562
615
  if found:
563
616
  name = found[0].replace('_', '')
564
- elif main.alias and not is_function():
617
+ elif '.' not in name and main.alias and not is_function():
565
618
  name = f'{main.alias}.{name}'
566
619
  return name
567
620
 
@@ -632,7 +685,7 @@ class Having:
632
685
 
633
686
  def add(self, name: str, main:SQLObject):
634
687
  main.values[GROUP_BY][-1] += ' HAVING {} {}'.format(
635
- self.function.format(name, main), self.condition.expr
688
+ self.function.format(name, main), self.condition.content
636
689
  )
637
690
 
638
691
  @classmethod
@@ -1003,8 +1056,11 @@ class SQLParser(Parser):
1003
1056
  if not key in values:
1004
1057
  continue
1005
1058
  separator = self.class_type.get_separator(key)
1059
+ cls = {
1060
+ ORDER_BY: OrderBy, GROUP_BY: GroupBy
1061
+ }.get(key, Field)
1006
1062
  obj.values[key] = [
1007
- Field.format(fld, obj)
1063
+ cls.format(fld, obj)
1008
1064
  for fld in re.split(separator, values[key])
1009
1065
  if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
1010
1066
  ]
@@ -1066,7 +1122,11 @@ class CypherParser(Parser):
1066
1122
  if token in self.TOKEN_METHODS:
1067
1123
  return
1068
1124
  class_list = [Field]
1069
- if '$' in token:
1125
+ if '*' in token:
1126
+ token = token.replace('*', '')
1127
+ self.queries[-1].key_field = token
1128
+ return
1129
+ elif '$' in token:
1070
1130
  func_name, token = token.split('$')
1071
1131
  if func_name == 'count':
1072
1132
  if not token:
@@ -1090,10 +1150,13 @@ class CypherParser(Parser):
1090
1150
  def add_foreign_key(self, token: str, pk_field: str=''):
1091
1151
  curr, last = [self.queries[i] for i in (-1, -2)]
1092
1152
  if not pk_field:
1093
- if not last.values.get(SELECT):
1094
- raise IndexError(f'Primary Key not found for {last.table_name}.')
1095
- pk_field = last.values[SELECT][-1].split('.')[-1]
1096
- last.delete(pk_field, [SELECT], exact=True)
1153
+ if last.key_field:
1154
+ pk_field = last.key_field
1155
+ else:
1156
+ if not last.values.get(SELECT):
1157
+ raise IndexError(f'Primary Key not found for {last.table_name}.')
1158
+ pk_field = last.values[SELECT][-1].split('.')[-1]
1159
+ last.delete(pk_field, [SELECT], exact=True)
1097
1160
  if '{}' in token:
1098
1161
  foreign_fld = token.format(
1099
1162
  last.table_name.lower()
@@ -1411,6 +1474,88 @@ class NotSelectIN(SelectIN):
1411
1474
  condition_class = Not
1412
1475
 
1413
1476
 
1477
+ class CTE(Select):
1478
+ prefix = ''
1479
+
1480
+ def __init__(self, table_name: str, query_list: list[Select]):
1481
+ super().__init__(table_name)
1482
+ for query in query_list:
1483
+ query.break_lines = False
1484
+ self.query_list = query_list
1485
+ self.break_lines = False
1486
+
1487
+ def __str__(self) -> str:
1488
+ # ---------------------------------------------------------
1489
+ def justify(query: Select) -> str:
1490
+ result, line = [], ''
1491
+ keywords = '|'.join(KEYWORD)
1492
+ for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
1493
+ if len(line) >= 65:
1494
+ result.append(line)
1495
+ line = ''
1496
+ line += word
1497
+ if line:
1498
+ result.append(line)
1499
+ return '\n '.join(result)
1500
+ # ---------------------------------------------------------
1501
+ return 'WITH {}{} AS (\n {}\n){}'.format(
1502
+ self.prefix, self.table_name,
1503
+ '\nUNION ALL\n '.join(
1504
+ justify(q) for q in self.query_list
1505
+ ), super().__str__()
1506
+ )
1507
+
1508
+ class Recursive(CTE):
1509
+ prefix = 'RECURSIVE '
1510
+
1511
+ def __str__(self) -> str:
1512
+ if len(self.query_list) > 1:
1513
+ self.query_list[-1].values[FROM].append(
1514
+ f', {self.table_name} {self.alias}')
1515
+ return super().__str__()
1516
+
1517
+ @classmethod
1518
+ def create(cls, name: str, pattern: str, formula: str, init_value, format: str=''):
1519
+ SQLObject.ALIAS_FUNC = None
1520
+ def get_field(obj: SQLObject, pos: int) -> str:
1521
+ return obj.values[SELECT][pos].split('.')[-1]
1522
+ t1, t2 = detect(
1523
+ pattern*2, join_queries=False, format=format
1524
+ )
1525
+ pk_field = get_field(t1, 0)
1526
+ foreign_key = ''
1527
+ for num in re.findall(r'\[(\d+)\]', formula):
1528
+ num = int(num)
1529
+ if not foreign_key:
1530
+ foreign_key = get_field(t2, num-1)
1531
+ formula = formula.replace(f'[{num}]', '%')
1532
+ else:
1533
+ formula = formula.replace(f'[{num}]', get_field(t2, num-1))
1534
+ Where.eq(init_value).add(pk_field, t1)
1535
+ Where.formula(formula).add(foreign_key or pk_field, t2)
1536
+ return cls(name, [t1, t2])
1537
+
1538
+ def join(self, pattern: str, fields: list | str, format: str):
1539
+ if isinstance(fields, str):
1540
+ count = len( fields.split(',') )
1541
+ else:
1542
+ count = len(fields)
1543
+ queries = detect(
1544
+ pattern*count, join_queries=False, format=format
1545
+ )
1546
+ FieldList(fields, queries, ziped=True).add('', self)
1547
+ self.break_lines = True
1548
+
1549
+ def counter(self, name: str, start, increment: str='+1'):
1550
+ for i, query in enumerate(self.query_list):
1551
+ if i == 0:
1552
+ Field.add(f'{start} AS {name}', query)
1553
+ else:
1554
+ Field.add(f'({name}{increment}) AS {name}', query)
1555
+
1556
+
1557
+ # ----- Rules -----
1558
+
1414
1559
  class RulePutLimit(Rule):
1415
1560
  @classmethod
1416
1561
  def apply(cls, target: Select):
@@ -1525,7 +1670,7 @@ def parser_class(text: str) -> Parser:
1525
1670
  return None
1526
1671
 
1527
1672
 
1528
- def detect(text: str, join_queries: bool = True) -> Select:
1673
+ def detect(text: str, join_queries: bool = True, format: str='') -> Select | list[Select]:
1529
1674
  from collections import Counter
1530
1675
  parser = parser_class(text)
1531
1676
  if not parser:
@@ -1536,11 +1681,14 @@ def detect(text: str, join_queries: bool = True) -> Select:
1536
1681
  continue
1537
1682
  pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
1538
1683
  for begin, end in pos[::-1]:
1539
- new_name = f'{table}_{count}' # See set_table (line 45)
1684
+ new_name = f'{table}_{count}' # See set_table (line 55)
1540
1685
  Select.EQUIVALENT_NAMES[new_name] = table
1541
1686
  text = text[:begin] + new_name + '(' + text[end:]
1542
1687
  count -= 1
1543
1688
  query_list = Select.parse(text, parser)
1689
+ if format:
1690
+ for query in query_list:
1691
+ query.set_file_format(format)
1544
1692
  if not join_queries:
1545
1693
  return query_list
1546
1694
  result = query_list[0]
@@ -1549,11 +1697,3 @@ def detect(text: str, join_queries: bool = True) -> Select:
1549
1697
  return result
1550
1698
 
1551
1699
 
1552
- if __name__ == '__main__':
1553
- query = Select(
1554
- 'Tips t',
1555
- tip=[Field, Lag().over(day=OrderBy).As('last')],
1556
- diff=ExpressionField('Round(tip-last, 2) as {f}'),
1557
- Row_Number=gt(1)
1558
- )
1559
- print(query)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.1901
3
+ Version: 1.25.1021229
4
4
  Summary: Allows you to create objects for parts of SQL query commands. Also to combine these objects by joining them, adding or removing parts...
5
5
  Home-page: https://github.com/julio-cascalles/sql_blocks
6
6
  Author: Júlio Cascalles
@@ -98,10 +98,7 @@ query = Select('Movie m', title=Field,
98
98
  awards=contains("Oscar")
99
99
  )
100
100
  AND=Options(
101
- ..., name=contains(
102
- 'Chris',
103
- Position.StartsWith
104
- )
101
+ ..., name=startswith('Chris')
105
102
  )
106
103
  ```
107
104
 
@@ -115,6 +112,16 @@ based_on_book=Not.is_null()
115
112
  hash_tag=inside(['space', 'monster', 'gore'])
116
113
  ```
117
114
 
115
+ 3.6 -- Combining ExpressionField with Where condition:
116
+ * The **formula** method allows you to write an expression as a condition:
117
+ ```
118
+ query=Select(
119
+ 'Folks f2',
120
+ id=Where.formula('({af} = a.father OR {af} = a.mother)')
121
+ )
122
+ ```
123
+ > Results: `WHERE...f2.id = a.father OR f2.id = a.mother`
124
+
118
125
  ---
119
126
  ### 4 - A field can be two things at the same time:
120
127
 
@@ -143,6 +150,35 @@ FROM
143
150
  JOIN Cast c ON (a.cast = c.id)
144
151
  ```
145
152
 
153
+ ---
154
+ **5.1 Multiple tables without JOIN**
155
+ > Warning: This is **NOT** recommended! ⛔
156
+
157
+
158
+ #### Example:
159
+ singer = Select(
160
+ "Singer artist", id=PrimaryKey,
161
+ name=NamedField('artist_name')
162
+ )
163
+ album = Select (
164
+ "Album album",
165
+ name=NamedField('album_name'),
166
+ artist_id=Where.join(singer), # <===== 👀
167
+ )
168
+ **>> print(query)**
169
+
170
+ SELECT
171
+ album.name as album_name,
172
+ artist.name as artist_name,
173
+ album.year_recorded
174
+ FROM
175
+ Album album
176
+ ,Singer artist
177
+ WHERE
178
+ (album.artist_id = artist.id)
179
+
180
+
181
+
146
182
  ---
147
183
  ### 6 - The reverse process (parse):
148
184
  ```
@@ -373,7 +409,7 @@ m2 = Select(
373
409
  query = Select(
374
410
  'Installments i', due_date=Field, customer=Select(
375
411
  'Customer c', id=PrimaryKey,
376
- name=contains('Smith', Position.EndsWith)
412
+ name=endswith('Smith')
377
413
  )
378
414
  )
379
415
  print(query)
@@ -453,6 +489,7 @@ ORDER BY
453
489
  * `^` Put the field in the ORDER BY clause
454
490
  * `@` Immediately after the table name, it indicates the grouping field.
455
491
  * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
492
+ * `*` Sets the primary key field.
456
493
 
457
494
 
458
495
  ---
@@ -631,3 +668,121 @@ For example, if your query is going to run on Oracle, do the following:
631
668
 
632
669
  `Function.dialect = Dialect.ORACLE`
633
670
 
671
+ ---
672
+
673
+ ### 17 - CTE and Recursive classes
674
+
675
+ * **17.1 - _CTE class_**
676
+ ```
677
+ query = Select(
678
+ 'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
679
+ )
680
+ print( CTE('Metrics', [query]) )
681
+ ```
682
+ The result is...
683
+ ```
684
+ WITH Metrics AS (
685
+ SELECT Count(s.post), Sum(s.reaction) FROM SocialMedia s GROUP BY user
686
+ )SELECT * FROM Metrics
687
+ ```
688
+
689
+ * **17.2 - _Recursive class_**
690
+ ```
691
+ q1 = Select(
692
+ 'SocialMedia me', name=[ eq(MY_NAME), Field ]
693
+ )
694
+ q2 = Select(
695
+ 'SocialMedia you' name=Field, id=Where.formula('{af} = n.friend')
696
+ )
697
+ print( Recursive('Network', [q1, q2]) )
698
+ ```
699
+ The result is...
700
+ ```
701
+ WITH RECURSIVE Network AS (
702
+ SELECT me.name FROM SocialMedia me WHERE
703
+ me.name = 'Júlio Cascalles'
704
+ UNION ALL
705
+ SELECT you.name FROM SocialMedia you , Network n
706
+ WHERE you.id = n.friend
707
+ )SELECT * FROM Network
708
+ ```
709
+
710
+ * **17.2.1 - The `create` method** ... parameters :
711
+ - name: The name of the CTE
712
+ - pattern: A cypher script that defines the tables used
713
+ - formula: The format for `Where.formula` method _(*)_
714
+ - init_value: The value for the condition in the first table
715
+ - format (optional): If tables are files or internet hiperlinks, you may especify the extension and/or folder...
716
+ > Example:
717
+ ```
718
+ R = Recursive.create(
719
+ 'Route R', 'Flyght(departure, arrival)',
720
+ '[2] = R.[1]', 'JFK', format='.csv'
721
+ ) # ^^^--- Flyghts from JFK airport
722
+ ```
723
+ _...Creates a recursive CTE called Route, using Flyght table, where the recursivity condition is Flyght.arrival equals to Route.departure_
724
+ >> (*) -- Note that [1] and [2] refers to first field and second field. 😉
725
+
726
+ Result:
727
+
728
+ WITH RECURSIVE Route AS (
729
+ SELECT f1.departure, f1.arrival
730
+ FROM Flyght.csv f1
731
+ WHERE f1.departure = 'JFK'
732
+ UNION ALL
733
+ SELECT f2.departure, f2.arrival
734
+ FROM Flyght.csv f2
735
+ , Route R
736
+ WHERE f2.arrival = R.departure
737
+ )SELECT * FROM Route R
738
+
739
+ **17.2.2 - The `join` method**
740
+
741
+ In the previous example, if you add this code...
742
+ `R.join('Airport(*id,name)', 'departure, arrival', format='.csv')`
743
+
744
+ ...The result would be:
745
+
746
+ WITH RECURSIVE Route AS (
747
+ SELECT f1.departure, f1.arrival
748
+ FROM Flyght.csv f1
749
+ WHERE f1.departure = 'JFK'
750
+ UNION ALL
751
+ SELECT f2.departure, f2.arrival
752
+ FROM Flyght.csv f2
753
+ , Route R
754
+ WHERE f2.arrival = R.departure
755
+ )SELECT
756
+ a1.name, a2.name
757
+ FROM
758
+ Route R
759
+ JOIN Airport.csv a2 ON (R.arrival = a2.id)
760
+ JOIN Airport.csv a1 ON (R.departure = a1.id)
761
+
762
+
763
+ **17.2.3 - The `counter` method**
764
+ Adds an increment field in queries inside CTE:
765
+ > Examples:
766
+ * `R.counter('stops', 0)` # -- counter starts with 0 and increment +1
767
+ * `R2.counter('generation', 5, '- 1')` # -- for the code below...
768
+ ```
769
+ R2 = Recursive.create(
770
+ 'Ancestors a', 'People(id,name,father,mother,birth)',
771
+ '(% = a.father OR % = a.mother)', 32630, '.parquet'
772
+ )
773
+ ```
774
+ ...Results:
775
+
776
+ WITH RECURSIVE Ancestors AS (
777
+ SELECT p1.id, p1.name, p1.father, p1.mother, p1.birth,
778
+ 5 AS generation /* <<---- Most current generation ------------*/
779
+ FROM People.parquet p1 WHERE p1.id = 32630
780
+ UNION ALL
781
+ SELECT p2.id, p2.name, p2.father, p2.mother, p2.birth,
782
+ (generation- 1) AS generation /* <<-- Previous generation -----*/
783
+ FROM People.parquet p2 , Ancestors a WHERE (p2.id = a.father OR p2.id = a.mother)
784
+ )SELECT * FROM Ancestors a
785
+
786
+
787
+ >> Note: Comments added later.
788
+ ---
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=VW8n2nWu7RREnns8dmYruhlvzGBCA3EeuBKiXCjClmE,56975
3
+ sql_blocks-1.25.1021229.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-1.25.1021229.dist-info/METADATA,sha256=XXScfi4okZQLPPHvVvi2bd6YsvQQLb5ry2_mmOLThU4,19573
5
+ sql_blocks-1.25.1021229.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-1.25.1021229.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-1.25.1021229.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=U2bcs8SnXcGOlsY9rhB3lpPzzb-W4MsG64N7r0QjKEI,51822
3
- sql_blocks-1.25.1901.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-1.25.1901.dist-info/METADATA,sha256=MCkLq9JFvLiVAEJc_IhDLd3rExUKeVa04EtC9IwJwsA,15027
5
- sql_blocks-1.25.1901.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-1.25.1901.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-1.25.1901.dist-info/RECORD,,