sql-blocks 1.25.1801__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:
@@ -335,6 +346,9 @@ class Cast(Function):
335
346
  ...
336
347
 
337
348
 
349
+ FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
350
+
351
+
338
352
  class ExpressionField:
339
353
  def __init__(self, expr: str):
340
354
  self.expr = expr
@@ -357,15 +371,20 @@ class ExpressionField:
357
371
  class FieldList:
358
372
  separator = ','
359
373
 
360
- def __init__(self, fields: list=[], class_types = [Field]):
374
+ def __init__(self, fields: list=[], class_types = [Field], ziped: bool=False):
361
375
  if isinstance(fields, str):
362
376
  fields = [
363
377
  f.strip() for f in fields.split(self.separator)
364
378
  ]
365
379
  self.fields = fields
366
380
  self.class_types = class_types
381
+ self.ziped = ziped
367
382
 
368
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
369
388
  for field in self.fields:
370
389
  for class_type in self.class_types:
371
390
  class_type.add(field, main)
@@ -420,23 +439,23 @@ class Position(Enum):
420
439
  class Where:
421
440
  prefix = ''
422
441
 
423
- def __init__(self, expr: str):
424
- self.expr = expr
442
+ def __init__(self, content: str):
443
+ self.content = content
425
444
 
426
445
  @classmethod
427
446
  def __constructor(cls, operator: str, value):
428
- return cls(expr=f'{operator} {quoted(value)}')
447
+ return cls(f'{operator} {quoted(value)}')
429
448
 
430
449
  @classmethod
431
450
  def eq(cls, value):
432
451
  return cls.__constructor('=', value)
433
452
 
434
453
  @classmethod
435
- def contains(cls, content: str, pos: Position = Position.Middle):
454
+ def contains(cls, text: str, pos: Position = Position.Middle):
436
455
  return cls(
437
456
  "LIKE '{}{}{}'".format(
438
457
  '%' if pos != Position.StartsWith else '',
439
- content,
458
+ text,
440
459
  '%' if pos != Position.EndsWith else ''
441
460
  )
442
461
  )
@@ -467,9 +486,47 @@ class Where:
467
486
  values = ','.join(quoted(v) for v in values)
468
487
  return cls(f'IN ({values})')
469
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
+
470
517
  def add(self, name: str, main: SQLObject):
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
+ )
524
+ if func_type:
525
+ name = func_type.format('*', main)
526
+ elif not exists:
527
+ name = Field.format(name, main)
471
528
  main.values.setdefault(WHERE, []).append('{}{} {}'.format(
472
- self.prefix, Field.format(name, main), self.expr
529
+ self.prefix, name, self.content
473
530
  ))
474
531
 
475
532
 
@@ -477,6 +534,10 @@ eq, contains, gt, gte, lt, lte, is_null, inside = (
477
534
  getattr(Where, method) for method in
478
535
  ('eq', 'contains', 'gt', 'gte', 'lt', 'lte', 'is_null', 'inside')
479
536
  )
537
+ startswith, endswith = [
538
+ lambda x: contains(x, Position.StartsWith),
539
+ lambda x: contains(x, Position.EndsWith)
540
+ ]
480
541
 
481
542
 
482
543
  class Not(Where):
@@ -484,7 +545,7 @@ class Not(Where):
484
545
 
485
546
  @classmethod
486
547
  def eq(cls, value):
487
- return Where(expr=f'<> {quoted(value)}')
548
+ return Where(f'<> {quoted(value)}')
488
549
 
489
550
 
490
551
  class Case:
@@ -525,7 +586,7 @@ class Options:
525
586
  child: Where
526
587
  for field, child in self.__children.items():
527
588
  conditions.append(' {} {} '.format(
528
- Field.format(field, main), child.expr
589
+ Field.format(field, main), child.content
529
590
  ))
530
591
  main.values.setdefault(WHERE, []).append(
531
592
  '(' + logical_separator.join(conditions) + ')'
@@ -549,12 +610,11 @@ class Clause:
549
610
  def format(cls, name: str, main: SQLObject) -> str:
550
611
  def is_function() -> bool:
551
612
  diff = main.diff(SELECT, [name.lower()], True)
552
- FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
553
613
  return diff.intersection(FUNCTION_CLASS)
554
614
  found = re.findall(r'^_\d', name)
555
615
  if found:
556
616
  name = found[0].replace('_', '')
557
- elif main.alias and not is_function():
617
+ elif '.' not in name and main.alias and not is_function():
558
618
  name = f'{main.alias}.{name}'
559
619
  return name
560
620
 
@@ -625,7 +685,7 @@ class Having:
625
685
 
626
686
  def add(self, name: str, main:SQLObject):
627
687
  main.values[GROUP_BY][-1] += ' HAVING {} {}'.format(
628
- self.function.format(name, main), self.condition.expr
688
+ self.function.format(name, main), self.condition.content
629
689
  )
630
690
 
631
691
  @classmethod
@@ -996,8 +1056,11 @@ class SQLParser(Parser):
996
1056
  if not key in values:
997
1057
  continue
998
1058
  separator = self.class_type.get_separator(key)
1059
+ cls = {
1060
+ ORDER_BY: OrderBy, GROUP_BY: GroupBy
1061
+ }.get(key, Field)
999
1062
  obj.values[key] = [
1000
- Field.format(fld, obj)
1063
+ cls.format(fld, obj)
1001
1064
  for fld in re.split(separator, values[key])
1002
1065
  if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
1003
1066
  ]
@@ -1059,7 +1122,11 @@ class CypherParser(Parser):
1059
1122
  if token in self.TOKEN_METHODS:
1060
1123
  return
1061
1124
  class_list = [Field]
1062
- if '$' in token:
1125
+ if '*' in token:
1126
+ token = token.replace('*', '')
1127
+ self.queries[-1].key_field = token
1128
+ return
1129
+ elif '$' in token:
1063
1130
  func_name, token = token.split('$')
1064
1131
  if func_name == 'count':
1065
1132
  if not token:
@@ -1068,7 +1135,6 @@ class CypherParser(Parser):
1068
1135
  Count().As(token, extra_classes).add(pk_field, self.queries[-1])
1069
1136
  return
1070
1137
  else:
1071
- FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
1072
1138
  class_list = [ FUNCTION_CLASS[func_name] ]
1073
1139
  class_list += extra_classes
1074
1140
  FieldList(token, class_list).add('', self.queries[-1])
@@ -1084,10 +1150,13 @@ class CypherParser(Parser):
1084
1150
  def add_foreign_key(self, token: str, pk_field: str=''):
1085
1151
  curr, last = [self.queries[i] for i in (-1, -2)]
1086
1152
  if not pk_field:
1087
- if not last.values.get(SELECT):
1088
- raise IndexError(f'Primary Key not found for {last.table_name}.')
1089
- pk_field = last.values[SELECT][-1].split('.')[-1]
1090
- 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)
1091
1160
  if '{}' in token:
1092
1161
  foreign_fld = token.format(
1093
1162
  last.table_name.lower()
@@ -1405,6 +1474,88 @@ class NotSelectIN(SelectIN):
1405
1474
  condition_class = Not
1406
1475
 
1407
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
+
1408
1559
  class RulePutLimit(Rule):
1409
1560
  @classmethod
1410
1561
  def apply(cls, target: Select):
@@ -1519,7 +1670,7 @@ def parser_class(text: str) -> Parser:
1519
1670
  return None
1520
1671
 
1521
1672
 
1522
- def detect(text: str, join_queries: bool = True) -> Select:
1673
+ def detect(text: str, join_queries: bool = True, format: str='') -> Select | list[Select]:
1523
1674
  from collections import Counter
1524
1675
  parser = parser_class(text)
1525
1676
  if not parser:
@@ -1530,11 +1681,14 @@ def detect(text: str, join_queries: bool = True) -> Select:
1530
1681
  continue
1531
1682
  pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
1532
1683
  for begin, end in pos[::-1]:
1533
- new_name = f'{table}_{count}' # See set_table (line 45)
1684
+ new_name = f'{table}_{count}' # See set_table (line 55)
1534
1685
  Select.EQUIVALENT_NAMES[new_name] = table
1535
1686
  text = text[:begin] + new_name + '(' + text[end:]
1536
1687
  count -= 1
1537
1688
  query_list = Select.parse(text, parser)
1689
+ if format:
1690
+ for query in query_list:
1691
+ query.set_file_format(format)
1538
1692
  if not join_queries:
1539
1693
  return query_list
1540
1694
  result = query_list[0]
@@ -1543,15 +1697,3 @@ def detect(text: str, join_queries: bool = True) -> Select:
1543
1697
  return result
1544
1698
 
1545
1699
 
1546
- if __name__ == '__main__':
1547
- for dialect in Dialect:
1548
- Function.dialect = dialect
1549
- print(f'--------------{dialect.name}--------------')
1550
- query = Select(
1551
- 'Installments',
1552
- _=DateDiff(
1553
- Current_Date(),
1554
- 'due_date'
1555
- ).As('elapsed_time')
1556
- ).limit(10)
1557
- print(query)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.1801
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=iFS3drlQaazFkbBBFIH58RPZt4Lhe6R2Xqi_Ujef7Bg,51893
3
- sql_blocks-1.25.1801.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-1.25.1801.dist-info/METADATA,sha256=WnwFnhle4YXNyeC6dBVAnQmUqJiboT1CDhbvRHOmEQY,15027
5
- sql_blocks-1.25.1801.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-1.25.1801.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-1.25.1801.dist-info/RECORD,,