├── .gitignore
├── call-stack
├── call-stack.pdf
└── explode.py
├── closures-decorators
└── closures-decorators.pdf
├── concurrency
├── 1-threads-simple.py
├── 10-threads-inheriting.py
├── 11-processes-simple.py
├── 12-processes-queues.py
├── 13-processes-workers-actor.py
├── 14-processes-pool.py
├── 2-threads-with-arguments.py
├── 3-threads-thread-names.py
├── 4-threads-logging.py
├── 5-threads-thread-joining.py
├── 6-threads-is-alive.py
├── 7-threads-events.py
├── 8-threads-locks.py
├── 9-threads-locks-2.py
└── concurrency.pdf
├── contextmanagers
└── contextmanagers.pdf
├── flasktemplate
└── flask3-connexion3.pdf
├── generators-references
└── generators-references-assignment.pdf
├── healthsecurityagency
├── ukhsa.odp
└── ukhsa.pdf
├── listcomprehensions
└── listcomprehensions.pdf
├── modules-import
├── dynamic.py
├── foo.py
├── modules-import.pdf
├── new_stock
│ ├── __init__.py
│ ├── formatter.py
│ ├── reader.py
│ └── stock.py
├── proxy.py
├── stock.py
└── stocks.csv
├── object-model
└── python-object-model.pdf
├── pytest
├── Pipfile
├── Pipfile.lock
├── app
│ ├── __init__.py
│ ├── api-spec.yaml
│ └── config.py
├── conftest.py
├── pytest.ini
├── pytest.pdf
├── test_eight.py
├── test_first.py
├── test_first_class.py
├── test_five.py
├── test_four.py
├── test_second.py
├── test_seven.py
├── test_six.py
└── test_third.py
└── unicode-regex
├── regex-exercises
├── regex_exercises.py
├── regex_exercises_solutions.py
└── test_regex.py
└── unicode-regex.pdf
/.gitignore:
--------------------------------------------------------------------------------
1 | .~*
2 | .idea
3 | __pycache__
4 |
5 |
--------------------------------------------------------------------------------
/call-stack/call-stack.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/call-stack/call-stack.pdf
--------------------------------------------------------------------------------
/call-stack/explode.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def foo():
5 | y = 57
6 | a = 'hello'
7 | try:
8 | bar()
9 | except TypeError:
10 | print('Ouch bar')
11 | print('done foo')
12 |
13 |
14 | def bar():
15 | x = 33
16 | v = 99
17 | baz()
18 | print('done bar')
19 |
20 | def baz():
21 | foo = 999
22 | try:
23 | bam()
24 | except ValueError:
25 | print('Ouch bam')
26 | print('done baz')
27 |
28 | def bam():
29 | data = {"here": "is", "some": "data"}
30 | raise RuntimeError('oh dear')
31 |
32 |
33 | if __name__ == '__main__':
34 | foo()
35 |
--------------------------------------------------------------------------------
/closures-decorators/closures-decorators.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/closures-decorators/closures-decorators.pdf
--------------------------------------------------------------------------------
/concurrency/1-threads-simple.py:
--------------------------------------------------------------------------------
1 | import threading
2 |
3 | def worker():
4 | """thread worker function"""
5 | print('Worker')
6 | return
7 |
8 | threads = []
9 | for i in range(5):
10 | t = threading.Thread(target=worker)
11 | threads.append(t)
12 | t.start()
--------------------------------------------------------------------------------
/concurrency/10-threads-inheriting.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import queue
3 | import time, random
4 |
5 | WORKERS = 2
6 |
7 | # Subclassing Thread
8 | class Worker(threading.Thread):
9 |
10 | def __init__(self, num, queue):
11 | self._num = num
12 | self._queue = queue
13 | super().__init__()
14 |
15 | def run(self):
16 | while 1:
17 | item = self._queue.get()
18 | if item is None:
19 | print(f"Worker {self._num} completed.")
20 | break # reached end of queue
21 |
22 | # pretend we're doing something that takes 10—100 ms
23 | time.sleep(random.randint(10, 100) / 1000.0)
24 |
25 | print(f"Task {item} finished from worker {self._num}.")
26 |
27 | queue = queue.Queue(0)
28 |
29 | workers = []
30 | for i in range(WORKERS):
31 | worker = Worker(i, queue)
32 | worker.start() # start a worker
33 | workers.append(worker)
34 |
35 | for i in range(10):
36 | queue.put(i)
37 |
38 | for i in range(WORKERS):
39 | queue.put(None) # add end-of-queue markers
40 |
--------------------------------------------------------------------------------
/concurrency/11-processes-simple.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 |
3 |
4 | def worker(i):
5 | """worker function"""
6 | print(f'Worker: {i}')
7 |
8 |
9 | if __name__ == '__main__':
10 | jobs = []
11 | for i in range(5):
12 | p = multiprocessing.Process(target=worker, args=(i,))
13 | jobs.append(p)
14 | p.start()
--------------------------------------------------------------------------------
/concurrency/12-processes-queues.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 |
3 |
4 | class MyFancyClass:
5 |
6 | def __init__(self, name):
7 | self.name = name
8 |
9 | def do_something(self):
10 | proc_name = multiprocessing.current_process().name
11 | print(f'Doing something fancy in {proc_name} for {self.name}!')
12 |
13 |
14 | def worker(q):
15 | obj = q.get()
16 | obj.do_something()
17 |
18 |
19 | if __name__ == '__main__':
20 | queue = multiprocessing.Queue()
21 |
22 | p = multiprocessing.Process(target=worker, args=(queue,))
23 | p.start()
24 |
25 | queue.put(MyFancyClass('Fancy Dan'))
26 |
27 | # Wait for the worker to finish
28 | queue.close()
29 | queue.join_thread()
30 | p.join()
31 |
--------------------------------------------------------------------------------
/concurrency/13-processes-workers-actor.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 | import random
3 | import time
4 |
5 |
6 | def worker(number, tasks, results):
7 | print(f"Worker {number} started")
8 |
9 | # Loop fetching tasks from the queue
10 | while True:
11 | task = tasks.get()
12 |
13 | if task is None:
14 | # Time to die
15 | print(f"Worker {number} shutting down")
16 | tasks.task_done()
17 | return
18 |
19 | # Perform the task
20 | answer = task()
21 | tasks.task_done()
22 | results.put((number, answer))
23 |
24 |
25 | def job():
26 | """Importable job for the workers"""
27 | # Sleep for up to a second
28 | time.sleep(random.randint(0, 10) / 10)
29 | # return the result of some onerous calculation
30 | return random.randint(0, 100)
31 |
32 |
33 | if __name__ == '__main__':
34 | # Establish communication queues
35 | tasks = multiprocessing.JoinableQueue()
36 | results = multiprocessing.Queue()
37 |
38 | # Start the workers
39 | num_workers = multiprocessing.cpu_count() * 2
40 |
41 | workers = [
42 | multiprocessing.Process(target=worker, args=(i, tasks, results))
43 | for i in range(num_workers)
44 | ]
45 | for w in workers:
46 | w.start()
47 |
48 | # Enqueue jobs
49 | num_jobs = 100
50 | for i in range(num_jobs):
51 | tasks.put(job)
52 |
53 | # Add a poison pill for each worker
54 | for i in range(num_workers):
55 | tasks.put(None)
56 |
57 | # Wait for all the tasks to finish
58 | tasks.join()
59 |
60 | # Start printing results
61 | while num_jobs:
62 | worker, result = results.get()
63 | print(f'Worker: {worker} Result: {result}')
64 | num_jobs -= 1
65 |
--------------------------------------------------------------------------------
/concurrency/14-processes-pool.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 |
3 |
4 | def do_calculation(data):
5 | return data * 2
6 |
7 |
8 | def start_process():
9 | print('Starting', multiprocessing.current_process().name)
10 |
11 |
12 | if __name__ == '__main__':
13 | inputs = list(range(10))
14 | print('Input :', inputs)
15 |
16 | builtin_outputs = [do_calculation(val) for val in inputs]
17 | print('Built-in:', builtin_outputs)
18 |
19 | pool_size = multiprocessing.cpu_count() * 2
20 | pool = multiprocessing.Pool(
21 | processes=pool_size,
22 | initializer=start_process,
23 | )
24 | pool_outputs = pool.map(do_calculation, inputs)
25 | pool.close() # no more tasks
26 | pool.join() # wrap up current tasks
27 |
28 | print('Pool :', pool_outputs)
29 |
--------------------------------------------------------------------------------
/concurrency/2-threads-with-arguments.py:
--------------------------------------------------------------------------------
1 | import threading
2 |
3 |
4 | def worker(num):
5 | """thread worker function"""
6 | print('Worker: %s' % num)
7 | return
8 |
9 |
10 | threads = []
11 | for i in range(5):
12 | t = threading.Thread(target=worker, args=(i,))
13 | threads.append(t)
14 | t.start()
15 |
--------------------------------------------------------------------------------
/concurrency/3-threads-thread-names.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import time
3 |
4 |
5 | def worker():
6 | print(threading.current_thread().name, 'Starting')
7 | time.sleep(2)
8 | print(threading.current_thread().name, 'Exiting')
9 |
10 | def my_service():
11 | print(threading.current_thread().name, 'Starting')
12 | time.sleep(3)
13 | print(threading.current_thread().name, 'Exiting')
14 |
15 |
16 | t = threading.Thread(name='my_service', target=my_service)
17 | w = threading.Thread(name='worker', target=worker)
18 | w2 = threading.Thread(target=worker) # use default name
19 |
20 | w.start()
21 | w2.start()
22 | t.start()
--------------------------------------------------------------------------------
/concurrency/4-threads-logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import threading
3 | import time
4 |
5 | logging.basicConfig(
6 | level=logging.DEBUG,
7 | format='[%(levelname)s] (%(threadName)-10s) %(message)s',
8 | )
9 |
10 |
11 | def worker():
12 | logging.debug('Starting')
13 | time.sleep(2)
14 | logging.debug('Exiting')
15 |
16 |
17 | def my_service():
18 | logging.debug('Starting')
19 | time.sleep(3)
20 | logging.debug('Exiting')
21 |
22 |
23 | t = threading.Thread(name='my_service', target=my_service)
24 | w = threading.Thread(name='worker', target=worker)
25 | w2 = threading.Thread(target=worker) # use default name
26 |
27 | w.start()
28 | w2.start()
29 | t.start()
--------------------------------------------------------------------------------
/concurrency/5-threads-thread-joining.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import time
3 | import logging
4 |
5 | logging.basicConfig(
6 | level=logging.DEBUG,
7 | format='(%(threadName)-10s) %(message)s',
8 | )
9 |
10 |
11 | def thread():
12 | logging.debug('Starting')
13 | time.sleep(2)
14 | logging.debug('Exiting')
15 |
16 |
17 | t = threading.Thread(name='worker', target=thread)
18 |
19 | t.start()
20 | t.join()
--------------------------------------------------------------------------------
/concurrency/6-threads-is-alive.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import time
3 | import logging
4 |
5 | logging.basicConfig(
6 | level=logging.DEBUG,
7 | format='(%(threadName)-10s) %(message)s',
8 | )
9 |
10 |
11 | def worker():
12 | logging.debug('Starting')
13 | time.sleep(2)
14 | logging.debug('Exiting')
15 |
16 |
17 | t = threading.Thread(name='worker', target=worker)
18 | t.start()
19 |
20 | t.join(1)
21 | print('t.is_alive()', t.is_alive())
22 | t.join()
23 | print('t.is_alive()', t.is_alive())
--------------------------------------------------------------------------------
/concurrency/7-threads-events.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import threading
3 | import time
4 |
5 | logging.basicConfig(
6 | level=logging.DEBUG,
7 | format='(%(threadName)-10s) %(message)s',
8 | )
9 |
10 |
11 | def wait_for_event(e):
12 | """Wait for the event to be set before doing anything"""
13 | logging.debug('wait_for_event starting')
14 | event_is_set = e.wait()
15 | logging.debug('event set: %s', event_is_set)
16 |
17 |
18 | def wait_for_event_timeout(e, t):
19 | """Wait t seconds and then timeout"""
20 | while not e.is_set():
21 | logging.debug('wait_for_event_timeout starting')
22 | event_is_set = e.wait(t)
23 | logging.debug('event set: %s', event_is_set)
24 | if event_is_set:
25 | logging.debug('processing event')
26 | else:
27 | logging.debug('doing other work')
28 |
29 |
30 | e = threading.Event()
31 | t1 = threading.Thread(name='block',
32 | target=wait_for_event,
33 | args=(e,))
34 | t1.start()
35 |
36 | t2 = threading.Thread(name='non-block',
37 | target=wait_for_event_timeout,
38 | args=(e, 2))
39 | t2.start()
40 |
41 | logging.debug('Waiting before calling Event.set()')
42 | time.sleep(3)
43 | e.set()
44 | logging.debug('Event is set')
45 |
--------------------------------------------------------------------------------
/concurrency/8-threads-locks.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import random
3 | import threading
4 | import time
5 |
6 | logging.basicConfig(
7 | level=logging.DEBUG,
8 | format='(%(threadName)-10s) %(message)s',
9 | )
10 |
11 |
12 | class Counter:
13 | def __init__(self, start=0):
14 | self.lock = threading.Lock()
15 | self.value = start
16 |
17 | def increment(self):
18 | logging.debug('Waiting for lock')
19 | with self.lock:
20 | logging.debug('Acquired lock')
21 | self.value = self.value + 1
22 |
23 |
24 | def worker(c):
25 | for i in range(2):
26 | pause = random.random()
27 | logging.debug('Sleeping %0.02f', pause)
28 | time.sleep(pause)
29 | c.increment()
30 | logging.debug('Done')
31 |
32 |
33 | counter = Counter()
34 | for i in range(2):
35 | t = threading.Thread(target=worker, args=(counter,))
36 | t.start()
37 |
38 | logging.debug('Waiting for worker threads')
39 | main_thread = threading.current_thread()
40 |
41 | # Loop over all the threads (except the main thread) and join them
42 | for t in threading.enumerate():
43 | if t is not main_thread:
44 | t.join()
45 | logging.debug('Counter: %d', counter.value)
46 |
--------------------------------------------------------------------------------
/concurrency/9-threads-locks-2.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import threading
3 | import time
4 |
5 | logging.basicConfig(
6 | level=logging.DEBUG,
7 | format='(%(threadName)-10s) %(message)s',
8 | )
9 |
10 |
11 | def lock_holder(lock):
12 | logging.debug('Starting')
13 | while True:
14 | with lock:
15 | logging.debug('Holding')
16 | time.sleep(0.5)
17 |
18 | logging.debug('Not holding')
19 | time.sleep(0.5)
20 | return
21 |
22 |
23 | def worker(lock):
24 | logging.debug('Starting')
25 | num_tries = 0
26 | num_acquires = 0
27 | while num_acquires < 3:
28 | time.sleep(0.5)
29 | logging.debug('Trying to acquire')
30 |
31 | # Manually calling lock.acquire with a timeout
32 | have_it = lock.acquire(0)
33 | try:
34 | num_tries += 1
35 | if have_it:
36 | logging.debug('Iteration %d: Acquired', num_tries)
37 | num_acquires += 1
38 | else:
39 | logging.debug('Iteration %d: Not acquired', num_tries)
40 | finally:
41 | if have_it:
42 | lock.release()
43 | logging.debug('Done after %d iterations', num_tries)
44 |
45 |
46 | lock = threading.Lock()
47 |
48 | # Note: in Python 3.10 Thread will take the name from the target
49 | # Automatically if you don't pass one in
50 | holder = threading.Thread(target=lock_holder, args=(lock,), name='LockHolder')
51 |
52 | # Must be set before start is called, or can be passed as an argument
53 | # to the Thread constructor
54 | holder.daemon = True
55 | holder.start()
56 |
57 | worker = threading.Thread(target=worker, args=(lock,), name='Worker')
58 | worker.start()
59 |
--------------------------------------------------------------------------------
/concurrency/concurrency.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/concurrency/concurrency.pdf
--------------------------------------------------------------------------------
/contextmanagers/contextmanagers.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/contextmanagers/contextmanagers.pdf
--------------------------------------------------------------------------------
/flasktemplate/flask3-connexion3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/flasktemplate/flask3-connexion3.pdf
--------------------------------------------------------------------------------
/generators-references/generators-references-assignment.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/generators-references/generators-references-assignment.pdf
--------------------------------------------------------------------------------
/healthsecurityagency/ukhsa.odp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/healthsecurityagency/ukhsa.odp
--------------------------------------------------------------------------------
/healthsecurityagency/ukhsa.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/healthsecurityagency/ukhsa.pdf
--------------------------------------------------------------------------------
/listcomprehensions/listcomprehensions.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/listcomprehensions/listcomprehensions.pdf
--------------------------------------------------------------------------------
/modules-import/dynamic.py:
--------------------------------------------------------------------------------
1 | _attrs = ['csv', 'importlib', 'inspect']
2 |
3 | def __dir__():
4 | return _attrs
5 |
6 | def __getattr__(name):
7 | if name not in _attrs:
8 | raise AttributeError(name)
9 |
10 |
11 |
--------------------------------------------------------------------------------
/modules-import/foo.py:
--------------------------------------------------------------------------------
1 | print(__name__)
--------------------------------------------------------------------------------
/modules-import/modules-import.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/modules-import/modules-import.pdf
--------------------------------------------------------------------------------
/modules-import/new_stock/__init__.py:
--------------------------------------------------------------------------------
1 | from new_stock.stock import Stock
2 | from new_stock.reader import read_stock_csv
3 | from new_stock.formatter import (
4 | print_stocks, print_table, TableFormatter,
5 | TextTableFormatter, HTMLTableFormatter
6 | )
7 |
8 | __all__ = [
9 | 'Stock', 'read_stock_csv', 'print_stocks', 'print_table',
10 | 'TableFormatter', 'TextTableFormatter', 'HTMLTableFormatter'
11 | ]
12 |
--------------------------------------------------------------------------------
/modules-import/new_stock/formatter.py:
--------------------------------------------------------------------------------
1 | # formatters
2 |
3 | def print_stocks(records, formatter=None):
4 | if formatter is None:
5 | formatter = TextTableFormatter()
6 | return print_table(records, ["name", "shares", "price"], formatter)
7 |
8 |
9 | def print_table(records, fields, formatter):
10 | formatter.headings(fields)
11 | for r in records:
12 | rowdata = [getattr(r, fieldname) for fieldname in fields]
13 | formatter.row(rowdata)
14 |
15 |
16 | class TableFormatter:
17 | def headings(self, headers):
18 | raise NotImplementedError()
19 |
20 | def row(self, rowdata):
21 | raise NotImplementedError()
22 |
23 |
24 | class TextTableFormatter(TableFormatter):
25 | def headings(self, headers):
26 | print(' '.join('%10s' % h for h in headers))
27 | print(('-' * 10 + ' ') * len(headers))
28 |
29 | def row(self, rowdata):
30 | print(' '.join('%10s' % d for d in rowdata))
31 |
32 |
33 | class HTMLTableFormatter(TableFormatter):
34 | def headings(self, headers):
35 | print('
', end=' ')
36 | for h in headers:
37 | print('%s | ' % h, end=' ')
38 | print('
')
39 |
40 | def row(self, rowdata):
41 | print('', end=' ')
42 | for d in rowdata:
43 | print('%s | ' % d, end=' ')
44 | print('
')
45 |
--------------------------------------------------------------------------------
/modules-import/new_stock/reader.py:
--------------------------------------------------------------------------------
1 | import csv
2 | from new_stock.stock import Stock
3 |
4 | # CSV reading
5 |
6 | def read_stock_csv(filename):
7 | '''
8 | Read a CSV file into a list of instances
9 | '''
10 | records = []
11 | with open(filename) as f:
12 | rows = csv.reader(f)
13 | headers = next(rows)
14 | for row in rows:
15 | records.append(Stock.from_row(row))
16 | return records
17 |
--------------------------------------------------------------------------------
/modules-import/new_stock/stock.py:
--------------------------------------------------------------------------------
1 | # The stock code
2 |
3 | class Stock:
4 | _types = (str, int, float)
5 |
6 | def __init__(self, name, shares, price):
7 | self.name = name
8 | self.shares = shares
9 | self.price = price
10 |
11 | def __repr__(self):
12 | # Note: The !r format code produces the repr() string
13 | return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'
14 |
15 | def __eq__(self, other):
16 | return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
17 | (other.name, other.shares, other.price))
18 |
19 | @classmethod
20 | def from_row(cls, row):
21 | values = [func(val) for func, val in zip(cls._types, row)]
22 | return cls(*values)
23 |
24 | @property
25 | def shares(self):
26 | return self._shares
27 |
28 | @shares.setter
29 | def shares(self, value):
30 | if not isinstance(value, self._types[1]):
31 | raise TypeError(f'Expected {self._types[1].__name__}')
32 | if value < 0:
33 | raise ValueError('shares must be >= 0')
34 | self._shares = value
35 |
36 | @property
37 | def price(self):
38 | return self._price
39 |
40 | @price.setter
41 | def price(self, value):
42 | if not isinstance(value, self._types[2]):
43 | raise TypeError(f'Expected {self._types[2].__name__}')
44 | if value < 0:
45 | raise ValueError('price must be >= 0')
46 | self._price = value
47 |
48 | @property
49 | def cost(self):
50 | return self.shares * self.price
51 |
52 | def sell(self, nshares):
53 | self.shares -= nshares
54 |
--------------------------------------------------------------------------------
/modules-import/proxy.py:
--------------------------------------------------------------------------------
1 |
2 | def proxy(thing, attrs: list | set):
3 | attrs = set(attrs)
4 | class ProxyThing:
5 | def __getattr__(self, name):
6 | if name in attrs:
7 | return getattr(thing, name)
8 | raise AttributeError(name)
9 | def __setattr__(self, name, value):
10 | if name in attrs:
11 | return setattr(thing, name, value)
12 | raise AttributeError(name)
13 | def __delattr__(self, name, value):
14 | if name in attrs:
15 | return setattr(thing, name, value)
16 | raise AttributeError(name)
17 | def __dir__(self):
18 | return list(attrs)
19 | return ProxyThing()
20 |
--------------------------------------------------------------------------------
/modules-import/stock.py:
--------------------------------------------------------------------------------
1 | import csv
2 |
3 |
4 | # The stock code
5 |
6 | class Stock:
7 | _types = (str, int, float)
8 |
9 | def __init__(self, name, shares, price):
10 | self.name = name
11 | self.shares = shares
12 | self.price = price
13 |
14 | def __repr__(self):
15 | # Note: The !r format code produces the repr() string
16 | return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'
17 |
18 | def __eq__(self, other):
19 | return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
20 | (other.name, other.shares, other.price))
21 |
22 | @classmethod
23 | def from_row(cls, row):
24 | values = [func(val) for func, val in zip(cls._types, row)]
25 | return cls(*values)
26 |
27 | @property
28 | def shares(self):
29 | return self._shares
30 |
31 | @shares.setter
32 | def shares(self, value):
33 | if not isinstance(value, self._types[1]):
34 | raise TypeError(f'Expected {self._types[1].__name__}')
35 | if value < 0:
36 | raise ValueError('shares must be >= 0')
37 | self._shares = value
38 |
39 | @property
40 | def price(self):
41 | return self._price
42 |
43 | @price.setter
44 | def price(self, value):
45 | if not isinstance(value, self._types[2]):
46 | raise TypeError(f'Expected {self._types[2].__name__}')
47 | if value < 0:
48 | raise ValueError('price must be >= 0')
49 | self._price = value
50 |
51 | @property
52 | def cost(self):
53 | return self.shares * self.price
54 |
55 | def sell(self, nshares):
56 | self.shares -= nshares
57 |
58 |
59 | # CSV reading
60 |
61 | def read_stock_csv(filename):
62 | '''
63 | Read a CSV file into a list of instances
64 | '''
65 | records = []
66 | with open(filename) as f:
67 | rows = csv.reader(f)
68 | headers = next(rows)
69 | for row in rows:
70 | records.append(Stock.from_row(row))
71 | return records
72 |
73 |
74 | # formatters
75 |
76 | def print_stocks(records, formatter=None):
77 | if formatter is None:
78 | formatter = TextTableFormatter()
79 | return print_table(records, ["name", "shares", "price"], formatter)
80 |
81 |
82 | def print_table(records, fields, formatter):
83 | formatter.headings(fields)
84 | for r in records:
85 | rowdata = [getattr(r, fieldname) for fieldname in fields]
86 | formatter.row(rowdata)
87 |
88 |
89 | class TableFormatter:
90 | def headings(self, headers):
91 | raise NotImplementedError()
92 |
93 | def row(self, rowdata):
94 | raise NotImplementedError()
95 |
96 |
97 | class TextTableFormatter(TableFormatter):
98 | def headings(self, headers):
99 | print(' '.join('%10s' % h for h in headers))
100 | print(('-' * 10 + ' ') * len(headers))
101 |
102 | def row(self, rowdata):
103 | print(' '.join('%10s' % d for d in rowdata))
104 |
105 |
106 | class HTMLTableFormatter(TableFormatter):
107 | def headings(self, headers):
108 | print('', end=' ')
109 | for h in headers:
110 | print('%s | ' % h, end=' ')
111 | print('
')
112 |
113 | def row(self, rowdata):
114 | print('', end=' ')
115 | for d in rowdata:
116 | print('%s | ' % d, end=' ')
117 | print('
')
118 |
--------------------------------------------------------------------------------
/modules-import/stocks.csv:
--------------------------------------------------------------------------------
1 | "GOOG",100,390.10
2 | "IBM",428,99.36
3 | "AAPL",27,420.16
4 | "GOOG",64,420.13
5 |
--------------------------------------------------------------------------------
/object-model/python-object-model.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/object-model/python-object-model.pdf
--------------------------------------------------------------------------------
/pytest/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | connexion = {extras = ["flask", "swagger-ui", "uvicorn"]}
8 | flask = {extras = ["async"], version = ">=3.0.0"}
9 | uvloop = {version = "*", sys_platform = "!= 'win32'"}
10 | pytest = '*'
11 | pytest-asyncio = "*"
12 |
13 | [dev-packages]
14 |
15 | [requires]
16 | python_version = "3.10"
17 |
--------------------------------------------------------------------------------
/pytest/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "313717443a3fe14ee4a01eeab450f7ce571b2754f5bee8962c4106f914ed1b2d"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.10"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "a2wsgi": {
20 | "hashes": [
21 | "sha256:50e81ac55aa609fa2c666e42bacc25c424c8884ce6072f1a7e902114b7ee5d63",
22 | "sha256:f17da93bf5952e0b0938c87f261c52b7305ddfab1ff3c70dd10b4b76db3851d3"
23 | ],
24 | "version": "==1.10.4"
25 | },
26 | "anyio": {
27 | "hashes": [
28 | "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8",
29 | "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"
30 | ],
31 | "markers": "python_version >= '3.8'",
32 | "version": "==4.3.0"
33 | },
34 | "asgiref": {
35 | "hashes": [
36 | "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47",
37 | "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"
38 | ],
39 | "markers": "python_version >= '3.8'",
40 | "version": "==3.8.1"
41 | },
42 | "attrs": {
43 | "hashes": [
44 | "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
45 | "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"
46 | ],
47 | "markers": "python_version >= '3.7'",
48 | "version": "==23.2.0"
49 | },
50 | "blinker": {
51 | "hashes": [
52 | "sha256:5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6",
53 | "sha256:da44ec748222dcd0105ef975eed946da197d5bdf8bafb6aa92f5bc89da63fa25"
54 | ],
55 | "markers": "python_version >= '3.8'",
56 | "version": "==1.8.1"
57 | },
58 | "certifi": {
59 | "hashes": [
60 | "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
61 | "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
62 | ],
63 | "markers": "python_version >= '3.6'",
64 | "version": "==2024.2.2"
65 | },
66 | "charset-normalizer": {
67 | "hashes": [
68 | "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
69 | "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
70 | "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
71 | "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
72 | "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
73 | "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
74 | "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
75 | "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
76 | "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
77 | "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
78 | "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
79 | "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
80 | "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
81 | "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
82 | "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
83 | "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
84 | "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
85 | "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
86 | "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
87 | "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
88 | "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
89 | "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
90 | "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
91 | "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
92 | "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
93 | "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
94 | "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
95 | "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
96 | "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
97 | "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
98 | "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
99 | "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
100 | "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
101 | "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
102 | "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
103 | "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
104 | "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
105 | "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
106 | "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
107 | "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
108 | "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
109 | "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
110 | "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
111 | "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
112 | "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
113 | "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
114 | "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
115 | "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
116 | "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
117 | "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
118 | "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
119 | "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
120 | "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
121 | "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
122 | "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
123 | "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
124 | "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
125 | "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
126 | "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
127 | "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
128 | "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
129 | "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
130 | "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
131 | "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
132 | "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
133 | "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
134 | "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
135 | "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
136 | "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
137 | "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
138 | "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
139 | "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
140 | "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
141 | "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
142 | "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
143 | "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
144 | "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
145 | "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
146 | "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
147 | "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
148 | "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
149 | "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
150 | "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
151 | "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
152 | "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
153 | "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
154 | "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
155 | "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
156 | "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
157 | "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
158 | ],
159 | "markers": "python_full_version >= '3.7.0'",
160 | "version": "==3.3.2"
161 | },
162 | "click": {
163 | "hashes": [
164 | "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
165 | "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
166 | ],
167 | "markers": "python_version >= '3.7'",
168 | "version": "==8.1.7"
169 | },
170 | "connexion": {
171 | "extras": [
172 | "flask",
173 | "swagger-ui",
174 | "uvicorn"
175 | ],
176 | "hashes": [
177 | "sha256:6dba2e3d3920653a16d41b373ee8b104281d078c2a3916b773b575c8e919eb1b",
178 | "sha256:9172600de1fd315ca368d6005f6cd56672559f4c7a3b5b239f0b3d473a10758f"
179 | ],
180 | "markers": "python_version >= '3.8' and python_version < '4.0'",
181 | "version": "==3.0.6"
182 | },
183 | "exceptiongroup": {
184 | "hashes": [
185 | "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad",
186 | "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"
187 | ],
188 | "markers": "python_version < '3.11'",
189 | "version": "==1.2.1"
190 | },
191 | "flask": {
192 | "extras": [
193 | "async"
194 | ],
195 | "hashes": [
196 | "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3",
197 | "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"
198 | ],
199 | "markers": "python_version >= '3.8'",
200 | "version": "==3.0.3"
201 | },
202 | "h11": {
203 | "hashes": [
204 | "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
205 | "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
206 | ],
207 | "markers": "python_version >= '3.7'",
208 | "version": "==0.14.0"
209 | },
210 | "httpcore": {
211 | "hashes": [
212 | "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61",
213 | "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"
214 | ],
215 | "markers": "python_version >= '3.8'",
216 | "version": "==1.0.5"
217 | },
218 | "httptools": {
219 | "hashes": [
220 | "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563",
221 | "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142",
222 | "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d",
223 | "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b",
224 | "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4",
225 | "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb",
226 | "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658",
227 | "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084",
228 | "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2",
229 | "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97",
230 | "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837",
231 | "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3",
232 | "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58",
233 | "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da",
234 | "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d",
235 | "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90",
236 | "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0",
237 | "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1",
238 | "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2",
239 | "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e",
240 | "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0",
241 | "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf",
242 | "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc",
243 | "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3",
244 | "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503",
245 | "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a",
246 | "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3",
247 | "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949",
248 | "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84",
249 | "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb",
250 | "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a",
251 | "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f",
252 | "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e",
253 | "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81",
254 | "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185",
255 | "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"
256 | ],
257 | "version": "==0.6.1"
258 | },
259 | "httpx": {
260 | "hashes": [
261 | "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5",
262 | "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"
263 | ],
264 | "markers": "python_version >= '3.8'",
265 | "version": "==0.27.0"
266 | },
267 | "idna": {
268 | "hashes": [
269 | "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc",
270 | "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"
271 | ],
272 | "markers": "python_version >= '3.5'",
273 | "version": "==3.7"
274 | },
275 | "inflection": {
276 | "hashes": [
277 | "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
278 | "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
279 | ],
280 | "markers": "python_version >= '3.5'",
281 | "version": "==0.5.1"
282 | },
283 | "iniconfig": {
284 | "hashes": [
285 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
286 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
287 | ],
288 | "markers": "python_version >= '3.7'",
289 | "version": "==2.0.0"
290 | },
291 | "itsdangerous": {
292 | "hashes": [
293 | "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef",
294 | "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"
295 | ],
296 | "markers": "python_version >= '3.8'",
297 | "version": "==2.2.0"
298 | },
299 | "jinja2": {
300 | "hashes": [
301 | "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
302 | "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
303 | ],
304 | "markers": "python_version >= '3.7'",
305 | "version": "==3.1.3"
306 | },
307 | "jsonschema": {
308 | "hashes": [
309 | "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7",
310 | "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"
311 | ],
312 | "markers": "python_version >= '3.8'",
313 | "version": "==4.22.0"
314 | },
315 | "jsonschema-specifications": {
316 | "hashes": [
317 | "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc",
318 | "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"
319 | ],
320 | "markers": "python_version >= '3.8'",
321 | "version": "==2023.12.1"
322 | },
323 | "markupsafe": {
324 | "hashes": [
325 | "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
326 | "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
327 | "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
328 | "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
329 | "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
330 | "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
331 | "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
332 | "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
333 | "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
334 | "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
335 | "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
336 | "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
337 | "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
338 | "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
339 | "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
340 | "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
341 | "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
342 | "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
343 | "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
344 | "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
345 | "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
346 | "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
347 | "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
348 | "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
349 | "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
350 | "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
351 | "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
352 | "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
353 | "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
354 | "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
355 | "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
356 | "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
357 | "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
358 | "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
359 | "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
360 | "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
361 | "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
362 | "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
363 | "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
364 | "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
365 | "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
366 | "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
367 | "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
368 | "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
369 | "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
370 | "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
371 | "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
372 | "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
373 | "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
374 | "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
375 | "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
376 | "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
377 | "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
378 | "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
379 | "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
380 | "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
381 | "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
382 | "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
383 | "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
384 | "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
385 | ],
386 | "markers": "python_version >= '3.7'",
387 | "version": "==2.1.5"
388 | },
389 | "packaging": {
390 | "hashes": [
391 | "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
392 | "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
393 | ],
394 | "markers": "python_version >= '3.7'",
395 | "version": "==24.0"
396 | },
397 | "pluggy": {
398 | "hashes": [
399 | "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1",
400 | "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"
401 | ],
402 | "markers": "python_version >= '3.8'",
403 | "version": "==1.5.0"
404 | },
405 | "pytest": {
406 | "hashes": [
407 | "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233",
408 | "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"
409 | ],
410 | "index": "pypi",
411 | "markers": "python_version >= '3.8'",
412 | "version": "==8.2.0"
413 | },
414 | "pytest-asyncio": {
415 | "hashes": [
416 | "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a",
417 | "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"
418 | ],
419 | "index": "pypi",
420 | "markers": "python_version >= '3.8'",
421 | "version": "==0.23.6"
422 | },
423 | "python-dotenv": {
424 | "hashes": [
425 | "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca",
426 | "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"
427 | ],
428 | "version": "==1.0.1"
429 | },
430 | "python-multipart": {
431 | "hashes": [
432 | "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026",
433 | "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"
434 | ],
435 | "markers": "python_version >= '3.8'",
436 | "version": "==0.0.9"
437 | },
438 | "pyyaml": {
439 | "hashes": [
440 | "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5",
441 | "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
442 | "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df",
443 | "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
444 | "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
445 | "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
446 | "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595",
447 | "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
448 | "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
449 | "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
450 | "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290",
451 | "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9",
452 | "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
453 | "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6",
454 | "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
455 | "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
456 | "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
457 | "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6",
458 | "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
459 | "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
460 | "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
461 | "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0",
462 | "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
463 | "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
464 | "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
465 | "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28",
466 | "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4",
467 | "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
468 | "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
469 | "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef",
470 | "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
471 | "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd",
472 | "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3",
473 | "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0",
474 | "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515",
475 | "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c",
476 | "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c",
477 | "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924",
478 | "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34",
479 | "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
480 | "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
481 | "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
482 | "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54",
483 | "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
484 | "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b",
485 | "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
486 | "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
487 | "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
488 | "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585",
489 | "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d",
490 | "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"
491 | ],
492 | "markers": "python_version >= '3.6'",
493 | "version": "==6.0.1"
494 | },
495 | "referencing": {
496 | "hashes": [
497 | "sha256:191e936b0c696d0af17ad7430a3dc68e88bc11be6514f4757dc890f04ab05889",
498 | "sha256:8080727b30e364e5783152903672df9b6b091c926a146a759080b62ca3126cd6"
499 | ],
500 | "markers": "python_version >= '3.8'",
501 | "version": "==0.35.0"
502 | },
503 | "requests": {
504 | "hashes": [
505 | "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
506 | "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
507 | ],
508 | "markers": "python_version >= '3.7'",
509 | "version": "==2.31.0"
510 | },
511 | "rpds-py": {
512 | "hashes": [
513 | "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f",
514 | "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c",
515 | "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76",
516 | "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e",
517 | "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157",
518 | "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f",
519 | "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5",
520 | "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05",
521 | "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24",
522 | "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1",
523 | "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8",
524 | "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b",
525 | "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb",
526 | "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07",
527 | "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1",
528 | "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6",
529 | "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e",
530 | "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e",
531 | "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1",
532 | "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab",
533 | "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4",
534 | "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17",
535 | "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594",
536 | "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d",
537 | "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d",
538 | "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3",
539 | "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c",
540 | "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66",
541 | "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f",
542 | "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80",
543 | "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33",
544 | "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f",
545 | "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c",
546 | "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022",
547 | "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e",
548 | "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f",
549 | "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da",
550 | "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1",
551 | "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688",
552 | "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795",
553 | "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c",
554 | "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98",
555 | "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1",
556 | "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20",
557 | "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307",
558 | "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4",
559 | "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18",
560 | "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294",
561 | "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66",
562 | "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467",
563 | "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948",
564 | "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e",
565 | "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1",
566 | "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0",
567 | "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7",
568 | "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd",
569 | "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641",
570 | "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d",
571 | "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9",
572 | "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1",
573 | "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da",
574 | "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3",
575 | "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa",
576 | "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7",
577 | "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40",
578 | "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496",
579 | "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124",
580 | "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836",
581 | "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434",
582 | "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984",
583 | "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f",
584 | "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6",
585 | "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e",
586 | "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461",
587 | "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c",
588 | "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432",
589 | "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73",
590 | "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58",
591 | "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88",
592 | "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337",
593 | "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7",
594 | "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863",
595 | "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475",
596 | "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3",
597 | "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51",
598 | "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf",
599 | "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024",
600 | "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40",
601 | "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9",
602 | "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec",
603 | "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb",
604 | "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7",
605 | "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861",
606 | "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880",
607 | "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f",
608 | "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd",
609 | "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca",
610 | "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58",
611 | "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"
612 | ],
613 | "markers": "python_version >= '3.8'",
614 | "version": "==0.18.0"
615 | },
616 | "sniffio": {
617 | "hashes": [
618 | "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
619 | "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
620 | ],
621 | "markers": "python_version >= '3.7'",
622 | "version": "==1.3.1"
623 | },
624 | "starlette": {
625 | "hashes": [
626 | "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee",
627 | "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"
628 | ],
629 | "markers": "python_version >= '3.8'",
630 | "version": "==0.37.2"
631 | },
632 | "swagger-ui-bundle": {
633 | "hashes": [
634 | "sha256:20673c3431c8733d5d1615ecf79d9acf30cff75202acaf21a7d9c7f489714529",
635 | "sha256:f7526f7bb99923e10594c54247265839bec97e96b0438561ac86faf40d40dd57"
636 | ],
637 | "version": "==1.1.0"
638 | },
639 | "tomli": {
640 | "hashes": [
641 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
642 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
643 | ],
644 | "markers": "python_version < '3.11'",
645 | "version": "==2.0.1"
646 | },
647 | "typing-extensions": {
648 | "hashes": [
649 | "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0",
650 | "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"
651 | ],
652 | "markers": "python_version >= '3.8'",
653 | "version": "==4.11.0"
654 | },
655 | "urllib3": {
656 | "hashes": [
657 | "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
658 | "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
659 | ],
660 | "markers": "python_version >= '3.8'",
661 | "version": "==2.2.1"
662 | },
663 | "uvicorn": {
664 | "extras": [
665 | "standard"
666 | ],
667 | "hashes": [
668 | "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de",
669 | "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"
670 | ],
671 | "version": "==0.29.0"
672 | },
673 | "uvloop": {
674 | "hashes": [
675 | "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd",
676 | "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec",
677 | "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b",
678 | "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc",
679 | "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797",
680 | "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5",
681 | "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2",
682 | "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d",
683 | "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be",
684 | "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd",
685 | "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12",
686 | "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17",
687 | "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef",
688 | "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24",
689 | "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428",
690 | "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1",
691 | "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849",
692 | "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593",
693 | "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd",
694 | "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67",
695 | "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6",
696 | "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3",
697 | "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd",
698 | "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8",
699 | "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7",
700 | "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533",
701 | "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957",
702 | "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650",
703 | "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e",
704 | "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7",
705 | "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"
706 | ],
707 | "markers": "python_full_version >= '3.8.0' and sys_platform != 'win32'",
708 | "version": "==0.19.0"
709 | },
710 | "watchfiles": {
711 | "hashes": [
712 | "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc",
713 | "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365",
714 | "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0",
715 | "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e",
716 | "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124",
717 | "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c",
718 | "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317",
719 | "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094",
720 | "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7",
721 | "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235",
722 | "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c",
723 | "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c",
724 | "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c",
725 | "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235",
726 | "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293",
727 | "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa",
728 | "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef",
729 | "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19",
730 | "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8",
731 | "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d",
732 | "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915",
733 | "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429",
734 | "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097",
735 | "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe",
736 | "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0",
737 | "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d",
738 | "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99",
739 | "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1",
740 | "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a",
741 | "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895",
742 | "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94",
743 | "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562",
744 | "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab",
745 | "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360",
746 | "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1",
747 | "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7",
748 | "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f",
749 | "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03",
750 | "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01",
751 | "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58",
752 | "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052",
753 | "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e",
754 | "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765",
755 | "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6",
756 | "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137",
757 | "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85",
758 | "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca",
759 | "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f",
760 | "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214",
761 | "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7",
762 | "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7",
763 | "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3",
764 | "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b",
765 | "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7",
766 | "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6",
767 | "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994",
768 | "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9",
769 | "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec",
770 | "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128",
771 | "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c",
772 | "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2",
773 | "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078",
774 | "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3",
775 | "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e",
776 | "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a",
777 | "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6",
778 | "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49",
779 | "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b",
780 | "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28",
781 | "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9",
782 | "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586",
783 | "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400",
784 | "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165",
785 | "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303",
786 | "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"
787 | ],
788 | "version": "==0.21.0"
789 | },
790 | "websockets": {
791 | "hashes": [
792 | "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b",
793 | "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6",
794 | "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df",
795 | "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b",
796 | "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205",
797 | "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892",
798 | "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53",
799 | "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2",
800 | "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed",
801 | "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c",
802 | "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd",
803 | "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b",
804 | "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931",
805 | "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30",
806 | "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370",
807 | "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be",
808 | "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec",
809 | "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf",
810 | "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62",
811 | "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b",
812 | "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402",
813 | "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f",
814 | "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123",
815 | "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9",
816 | "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603",
817 | "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45",
818 | "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558",
819 | "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4",
820 | "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438",
821 | "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137",
822 | "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480",
823 | "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447",
824 | "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8",
825 | "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04",
826 | "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c",
827 | "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb",
828 | "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967",
829 | "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b",
830 | "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d",
831 | "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def",
832 | "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c",
833 | "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92",
834 | "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2",
835 | "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113",
836 | "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b",
837 | "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28",
838 | "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7",
839 | "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d",
840 | "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f",
841 | "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468",
842 | "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8",
843 | "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae",
844 | "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611",
845 | "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d",
846 | "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9",
847 | "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca",
848 | "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f",
849 | "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2",
850 | "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077",
851 | "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2",
852 | "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6",
853 | "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374",
854 | "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc",
855 | "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e",
856 | "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53",
857 | "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399",
858 | "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547",
859 | "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3",
860 | "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870",
861 | "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5",
862 | "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8",
863 | "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"
864 | ],
865 | "version": "==12.0"
866 | },
867 | "werkzeug": {
868 | "hashes": [
869 | "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795",
870 | "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"
871 | ],
872 | "markers": "python_version >= '3.8'",
873 | "version": "==3.0.2"
874 | }
875 | },
876 | "develop": {}
877 | }
878 |
--------------------------------------------------------------------------------
/pytest/app/__init__.py:
--------------------------------------------------------------------------------
1 | import connexion
2 | from connexion.middleware import MiddlewarePosition
3 | from connexion.options import SwaggerUIOptions
4 | from starlette.middleware.cors import CORSMiddleware
5 |
6 | from .config import Config as config
7 |
8 | def create_app():
9 | connexion_app = connexion.FlaskApp('__name__')
10 | flask_app = connexion_app.app
11 |
12 | flask_app.config.from_object(config)
13 |
14 | origins = "*"
15 | if flask_app.config.get("CORS_ORIGINS") is not None:
16 | origins = flask_app.config["CORS_ORIGINS"]
17 |
18 | connexion_app.add_middleware(
19 | CORSMiddleware,
20 | position=MiddlewarePosition.BEFORE_EXCEPTION,
21 | allow_origins=[origins],
22 | allow_credentials=True,
23 | allow_methods=["*"],
24 | allow_headers=["*"],
25 | )
26 | options = SwaggerUIOptions(
27 | swagger_ui=True,
28 | swagger_ui_template_arguments={"SwaggerUITitle": {}, "title": "Swagger UI"},
29 | )
30 | connexion_app.add_api(
31 | specification='./app/api-spec.yaml',
32 | base_path=None,
33 | swagger_ui_options=options,
34 | pass_context_arg_name="request",
35 | )
36 |
37 | return connexion_app
38 |
39 | async def healthz_live():
40 | return {"response": "Healthy"}
41 |
42 |
43 | async def get_salesforce_client(username, password):
44 | raise NotImplementedError
45 |
46 | async def get_data(key):
47 | client = await get_salesforce_client(config.SF_USERNAME, config.SF_PASSWORD)
48 | try:
49 | result = await client.get_data(key)
50 | except IOError as e:
51 | return {"error": str(e)}
52 | return {"result": result}
53 |
--------------------------------------------------------------------------------
/pytest/app/api-spec.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.0
2 |
3 | info:
4 | title: Example app
5 | version: "0.0.0"
6 | paths:
7 | /healthz/live:
8 | get:
9 | operationId: app.healthz_live
10 | summary: Liveness check
11 | tags:
12 | - healthcheck
13 | responses:
14 | "200": {description: Success}
15 | "500": {description: Failure}
16 | /get-data:
17 | get:
18 | operationId: app.get_data
19 | summary: Fetch all the data
20 | tags:
21 | - getdata
22 | parameters:
23 | - name: key
24 | in: query
25 | required: true
26 | schema:
27 | type: string
28 | responses:
29 | "200": {description: Success}
30 | "500": {description: Failure}
31 | components:
32 | schemas: {}
33 |
--------------------------------------------------------------------------------
/pytest/app/config.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class Config:
4 | CORS_ORIGIN = "*"
5 |
6 | SF_USERNAME = "someone@somewhere.com"
7 | SF_PASSWORD = "SomePassword23!"
8 |
--------------------------------------------------------------------------------
/pytest/conftest.py:
--------------------------------------------------------------------------------
1 | from app import create_app
2 | from app.config import Config
3 |
4 | import pytest
5 |
6 | from unittest.mock import patch, AsyncMock
7 | import time
8 |
9 |
10 | @pytest.fixture
11 | def app(event_loop):
12 | return create_app()
13 |
14 | @pytest.fixture
15 | def test_client(app):
16 | return app.test_client()
17 |
18 | @pytest.fixture
19 | def config():
20 | return Config
21 |
22 |
23 | @pytest.fixture
24 | def mock_get_sf_client():
25 | with patch('app.get_salesforce_client', mock_class=AsyncMock) as mock_get_sf_client:
26 | yield mock_get_sf_client
27 | # Optional teardown of fixture after test completes
28 |
29 |
30 | @pytest.fixture
31 | def setup_data(request):
32 | print("\nSetting up test data...")
33 |
34 | # Add some data to the database
35 | data = 5
36 |
37 | # Define a finalizer function for teardown
38 | def finalizer():
39 | # Clean up the data
40 | print("\nCleaning up test data...")
41 |
42 | # Register the finalizer to ensure cleanup
43 | request.addfinalizer(finalizer)
44 | return data # Provide the data to the test
45 |
46 |
47 | @pytest.fixture(scope="function")
48 | def function_fixture():
49 | print(f"\nCalling function fixture at {time.time()}")
50 |
51 |
52 | @pytest.fixture(scope="class")
53 | def class_fixture():
54 | print(f"\nCalling class fixture at {time.time()}")
55 |
56 |
57 | @pytest.fixture(scope="module")
58 | def module_fixture():
59 | print(f"\nCalling module fixture at {time.time()}")
60 |
61 |
62 | @pytest.fixture(scope="session")
63 | def session_fixture():
64 | print(f"\nCalling session fixture at {time.time()}")
65 |
66 |
67 | # Magic to switch off slow tests by default
68 | # Unless you pass --runslow
69 |
70 |
71 | def pytest_addoption(parser):
72 | parser.addoption(
73 | "--runslow", action="store_true", default=False, help="run slow tests"
74 | )
75 |
76 |
77 | def pytest_configure(config):
78 | config.addinivalue_line("markers", "slow: mark test as slow to run")
79 |
80 |
81 | def pytest_collection_modifyitems(config, items):
82 | if config.getoption("--runslow"):
83 | # --runslow given in cli: do not skip slow tests
84 | return
85 | skip_slow = pytest.mark.skip(reason="need --runslow option to run")
86 | for item in items:
87 | if "slow" in item.keywords:
88 | item.add_marker(skip_slow)
89 |
--------------------------------------------------------------------------------
/pytest/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --strict-markers
3 | markers =
4 | slow: marks tests as slow
5 | smoke_test: tests of core functionality
--------------------------------------------------------------------------------
/pytest/pytest.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/pytest/pytest.pdf
--------------------------------------------------------------------------------
/pytest/test_eight.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class TestClassOne:
4 |
5 | def test_one(self, class_fixture, module_fixture, session_fixture):
6 | print(f"{self.__class__.__name__}: Test one")
7 | assert True
8 |
9 | def test_two(self, class_fixture, module_fixture, session_fixture):
10 | print(f"{self.__class__.__name__}: Test two")
11 | assert True
12 |
13 | def test_three(self, class_fixture, module_fixture, session_fixture):
14 | print(f"{self.__class__.__name__}: Test three")
15 | assert True
16 |
17 |
18 | class TestClassTwo:
19 |
20 | def test_one(self, class_fixture, module_fixture, session_fixture):
21 | print(f"{self.__class__.__name__}: Test one")
22 | assert True
23 |
24 | def test_two(self, class_fixture, module_fixture, session_fixture):
25 | print(f"{self.__class__.__name__}: Test two")
26 | assert True
27 |
28 | def test_three(self, class_fixture, module_fixture, session_fixture):
29 | print(f"{self.__class__.__name__}: Test three")
30 | assert True
31 |
32 |
33 | def test_function_one(function_fixture, module_fixture, session_fixture):
34 | print("test_function_one")
35 |
36 |
37 | def test_function_two(function_fixture, module_fixture, session_fixture):
38 | print("test_function_two")
39 |
--------------------------------------------------------------------------------
/pytest/test_first.py:
--------------------------------------------------------------------------------
1 |
2 | def test_function():
3 | result = 1 + 2
4 | assert result == 3
5 |
6 |
7 | def test_failing_test():
8 | result = 1 + 2
9 | assert result == 4
10 |
11 |
12 | def test_not_equals():
13 | bad_result = 'not this'
14 | assert 'actual result' != bad_result
15 |
16 | def test_comparisons():
17 | result = 6
18 | assert result > 5
19 | assert result < 9
20 | assert result >= 6
21 | assert result <= 6
22 |
23 |
24 | def test_is_not_operator():
25 | expected = object()
26 | actual = object()
27 |
28 | assert actual is not expected
29 |
30 |
31 | def test_is_operator():
32 | expected = object()
33 | actual = expected
34 |
35 | assert actual is expected
36 |
--------------------------------------------------------------------------------
/pytest/test_first_class.py:
--------------------------------------------------------------------------------
1 |
2 | class TestClass:
3 |
4 | def test_function(self):
5 | result = 1 + 2
6 | assert result == 3
7 |
8 |
9 | def test_not_equals(self):
10 | bad_result = 'not this'
11 | assert 'actual result' != bad_result
12 |
13 |
14 | def test_comparisons(self):
15 | result = 6
16 | assert result > 5
17 | assert result < 9
18 | assert result >= 6
19 | assert result <= 6
20 |
21 |
22 | def test_is_not_operator(self):
23 | expected = object()
24 | actual = object()
25 |
26 | assert actual is not expected
27 |
28 |
29 | def test_is_operator(self):
30 | expected = object()
31 | actual = expected
32 |
33 | assert actual is expected
34 |
--------------------------------------------------------------------------------
/pytest/test_five.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_exception():
4 | with pytest.raises(TypeError) as exc_info:
5 | "3" + 4
6 | expected_message = 'can only concatenate str (not "int") to str'
7 | assert str(exc_info.value) == expected_message
8 |
--------------------------------------------------------------------------------
/pytest/test_four.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import sys
3 | import time
4 |
5 | @pytest.mark.skipif(sys.platform=='win32', reason="Skipped on windoze")
6 | def test_not_windows():
7 | assert sys.platform != 'win32'
8 |
9 | @pytest.mark.smoke_test
10 | def test_core_functionality(test_client):
11 | def test_app(test_client):
12 | response = test_client.get('/healthz/live')
13 | assert response.status == 200
14 |
15 | @pytest.mark.slow
16 | def test_slow():
17 | time.sleep(10)
18 | assert True
19 |
20 |
21 | @pytest.mark.xfail
22 | def test_expected_failure():
23 | assert 3 == 2
24 |
25 |
--------------------------------------------------------------------------------
/pytest/test_second.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | def test_app(test_client):
4 | response = test_client.get('/healthz/live')
5 | assert response.json() == {'response': 'Healthy'}
6 |
--------------------------------------------------------------------------------
/pytest/test_seven.py:
--------------------------------------------------------------------------------
1 |
2 | def test_get_data_fixture(test_client, mock_get_sf_client, config):
3 | mock_client = mock_get_sf_client.return_value
4 | mock_client.get_data.return_value = 'some data'
5 |
6 | result = test_client.get('/get-data?key=value')
7 |
8 | assert result.status_code == 200
9 | assert result.json() == {'result': 'some data'}
10 |
11 | mock_get_sf_client.assert_called_once_with(config.SF_USERNAME, config.SF_PASSWORD)
12 | mock_client.get_data.assert_called_once_with('value')
13 |
14 |
15 | def test_setup_data_fixture(setup_data):
16 | assert setup_data == 5
17 |
--------------------------------------------------------------------------------
/pytest/test_six.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch, AsyncMock
2 |
3 |
4 | def test_get_data(config, test_client):
5 | with patch('app.get_salesforce_client', mock_class=AsyncMock) as mock_get_sf_client:
6 | mock_client = mock_get_sf_client.return_value
7 | mock_client.get_data.return_value = 'some data'
8 |
9 | result = test_client.get('/get-data?key=value')
10 |
11 | assert result.status_code == 200
12 | assert result.json() == {'result': 'some data'}
13 |
14 | mock_get_sf_client.assert_called_once_with(config.SF_USERNAME, config.SF_PASSWORD)
15 | mock_client.get_data.assert_called_once_with('value')
16 |
17 |
18 | def test_get_data_fails(test_client):
19 | with patch('app.get_salesforce_client', mock_class=AsyncMock) as mock_get_sf_client:
20 | mock_client = mock_get_sf_client.return_value
21 | mock_client.get_data.side_effect = IOError('authentication error')
22 |
23 | result = test_client.get('/get-data?key=value')
24 |
25 | assert result.json() == {'error': 'authentication error'}
26 |
--------------------------------------------------------------------------------
/pytest/test_third.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from operator import add
3 |
4 | sample_test_cases = [
5 | # (x, y, result)
6 | (1, 2, 3),
7 | (0, 0, 0),
8 | (-1, 2, 1),
9 | ]
10 |
11 | @pytest.mark.parametrize("x,y,result", sample_test_cases)
12 | def test_add(x, y, result):
13 | assert add(x, y) == result
14 |
--------------------------------------------------------------------------------
/unicode-regex/regex-exercises/regex_exercises.py:
--------------------------------------------------------------------------------
1 | """
2 | All the functions defined in this module should be written using the re module.
3 | They are tested by ``test_regex.py``.
4 | """
5 | import re
6 |
7 |
8 | def is_hex(string):
9 | """is_hex should return True if the given string only consists of digits and the
10 | letters A through to F."""
11 |
12 |
13 | def has_vowel(string):
14 | """has_vowel should return True if the given string has a vowel (any of the
15 | letters aeiou) and False if it doesn't"""
16 |
17 |
18 | def split_words(string):
19 | """Using re.split split the given string into a list of words."""
20 |
21 |
22 | def grouped_date(string):
23 | """Using groups return the year, month and day from a date formatted as
24 | yyyy-mm-dd"""
25 |
26 |
27 | def sub_digits(string):
28 | """Replace all digits with X using re.sub"""
29 |
30 |
31 | def date_rewrite(string):
32 | """Rewrite dates in "American" style format to ISO 86001 format.
33 | i.e. m/dd/yyyy or mm/d/yyyy should become yyyy-mm-dd"""
34 |
35 |
36 | def findall_airportcodes(string):
37 | """Find all the airport codes in the supplied string. An airport code is
38 | a three letter uppercase word."""
39 |
40 |
41 | def valid_times(string):
42 | """Write a single regular expression that will match valid 24 hour times only
43 | in the format 00:00 to 23:59, but not invalid times like 00:60 or 24:00."""
44 |
--------------------------------------------------------------------------------
/unicode-regex/regex-exercises/regex_exercises_solutions.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | """
4 | All the functions defined in this module should be written using the re module.
5 | They are tested by ``test_regex.py``.
6 | """
7 |
8 |
9 | def is_hex(string):
10 | """is_hex should return True if the given string only consists of digits and the
11 | letters A through to F."""
12 | if re.match(r'^[0-9A-F]+$', string) is not None:
13 | return True
14 | return False
15 |
16 |
17 | def has_vowel(string):
18 | """has_vowel should return True if the given string has a vowel (any of the
19 | letters aeiou) and False if it doesn't"""
20 | if re.search(r'[aeiou]', string) is not None:
21 | return True
22 | return False
23 |
24 |
25 | def split_words(string):
26 | """Using re.split split the given string into a list of words."""
27 | return re.split(r'[;.,\s_]+', string)
28 |
29 |
30 | def grouped_date(string):
31 | """Using groups return the year, month and day from a date formatted as
32 | yyyy-mm-dd"""
33 | return re.match(r'(\d{4})-(\d{2})-(\d{2})', string).groups()
34 |
35 |
36 | def sub_digits(string):
37 | """Replace all digits with X using re.sub"""
38 | return re.sub(r'\d', 'X', string)
39 |
40 |
41 | def date_rewrite(string):
42 | """Rewrite dates in "American" style format to ISO 86001 format.
43 | i.e. m/dd/yyyy or mm/d/yyyy should become yyyy-mm-dd"""
44 | return re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', string)
45 |
46 |
47 | def findall_airportcodes(string):
48 | """Find all the airport codes in the supplied string. An airport code is
49 | a three letter uppercase word."""
50 | return re.findall(r'\b[A-Z]{3}\b', string)
51 |
52 |
53 | def valid_times(string):
54 | """Write a single regular expression that will match valid 24 hour times only
55 | in the format 00:00 to 23:59, but not invalid times like 00:60 or 24:00."""
56 | if re.match(r'[01]\d:[0-5]\d|2[0-3]:[0-5]\d', string) is not None:
57 | return True
58 | return False
59 |
--------------------------------------------------------------------------------
/unicode-regex/regex-exercises/test_regex.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import regex_exercises
3 |
4 |
5 | class TestRegexExercises(unittest.TestCase):
6 |
7 | def test_is_hex(self):
8 | self.assertFalse(regex_exercises.is_hex(""))
9 | self.assertFalse(regex_exercises.is_hex("X"))
10 | self.assertTrue(regex_exercises.is_hex("99"))
11 | self.assertTrue(regex_exercises.is_hex('0123999AADDEEE'))
12 | self.assertTrue(regex_exercises.is_hex('A24'))
13 | self.assertFalse(regex_exercises.is_hex('G0123999AADDEEE'))
14 | self.assertFalse(regex_exercises.is_hex('123XXX'))
15 | # Bonus points for adding the correct flag to the regular
16 | # expression to make it work with lower case as well and
17 | # extending the test.
18 | # self.assertTrue(regex_exercises.is_hex('a24'))
19 |
20 | def test_has_vowel(self):
21 | self.assertFalse(regex_exercises.has_vowel(""))
22 | self.assertTrue(regex_exercises.has_vowel('hello'))
23 | self.assertFalse(regex_exercises.has_vowel('spryly'))
24 |
25 | def test_split_words(self):
26 | test_sentence = "A very, very_strange; sentence. indeed"
27 | expected = ["A", "very", "very", "strange", "sentence", "indeed"]
28 | self.assertEqual(regex_exercises.split_words(test_sentence), expected)
29 |
30 | def test_grouped_date(self):
31 | self.assertEqual(regex_exercises.grouped_date("2019-10-06"), ("2019", "10", "06"))
32 |
33 | def test_sub_digits(self):
34 | string = '1 AAA o0 99 ANBLD&C4CV 33'
35 | expected_output = 'X AAA oX XX ANBLD&CXCV XX'
36 | self.assertEqual(regex_exercises.sub_digits(string), expected_output)
37 |
38 | def test_date_rewrite(self):
39 | self.assertEqual(regex_exercises.date_rewrite('1/10/2018'), '2018-1-10')
40 | self.assertEqual(regex_exercises.date_rewrite('12/1/2018'), '2018-12-1')
41 | self.assertEqual(regex_exercises.date_rewrite('08/12/1974'), '1974-08-12')
42 |
43 | def test_findall_airportcodes(self):
44 | self.assertEqual(regex_exercises.findall_airportcodes(''), [])
45 | self.assertEqual(regex_exercises.findall_airportcodes('LAX'), ['LAX'])
46 | sentence = "This sentence has no aiport codes"
47 | self.assertEqual(regex_exercises.findall_airportcodes(sentence), [])
48 | sentence = "This sentence has DEN to DFW via ORD"
49 | self.assertEqual(regex_exercises.findall_airportcodes(sentence), ['DEN', 'DFW', 'ORD'])
50 | sentence = "This sentence lOOKs like two but only has LGW"
51 | self.assertEqual(regex_exercises.findall_airportcodes(sentence), ['LGW'])
52 |
53 | def test_valid_twenty_four_hour_time(self):
54 | self.assertTrue(regex_exercises.valid_times("00:00"))
55 | self.assertTrue(regex_exercises.valid_times("12:30"))
56 | self.assertTrue(regex_exercises.valid_times("23:59"))
57 | self.assertFalse(regex_exercises.valid_times(""))
58 | self.assertFalse(regex_exercises.valid_times("foo"))
59 | self.assertFalse(regex_exercises.valid_times("0000"))
60 | self.assertFalse(regex_exercises.valid_times("ab:cd"))
61 | self.assertFalse(regex_exercises.valid_times("23:61"))
62 | self.assertFalse(regex_exercises.valid_times("24:00"))
63 |
64 |
65 | if __name__ == '__main__':
66 | unittest.main()
67 |
--------------------------------------------------------------------------------
/unicode-regex/unicode-regex.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voidspace/talks/841607ffd8ed8bf9daaadd2a85ac5ae9b9edeac9/unicode-regex/unicode-regex.pdf
--------------------------------------------------------------------------------