Skip to content
This repository has been archived by the owner on Oct 8, 2023. It is now read-only.

Commit

Permalink
WIP 3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
frnkvieira committed Jun 30, 2018
1 parent c0c7690 commit a03cfba
Show file tree
Hide file tree
Showing 31 changed files with 282 additions and 173 deletions.
4 changes: 2 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ to be merged.

3) Bug fixes must include tests that fail/pass in respective versions.

4) PEP 8 must be followed strictly with the exception of the
line size which is acceptable to be a little bit longer when needed.
4) PEP 8 must be followed with the exception of the
max line size which is currently 120 instead of 80 chars wide.

### Reporting an issue

Expand Down
4 changes: 2 additions & 2 deletions docs/started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Make sure you are using `Python 3.6+` because Vibora takes
advantage of some new Python features.

1. Install Vibora: `pip install vibora`
1. Install Vibora: `pip install vibora[fast]`

> It's highly recommended to install Vibora inside a virtualenv.
> In case you have trouble with Vibora dependencies: `pip install vibora[pure]`
> In case you have trouble with Vibora dependencies: `pip install vibora` to install it without the extra libraries.

2. Create a file called `anything.py` with the following code:
Expand Down
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
cython==0.28.3
pendulum==2.0.2
cython==0.28.3
8 changes: 4 additions & 4 deletions samples/simple.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import time
from vibora import Vibora, Response
from vibora import Vibora
from vibora.responses import Response


app = Vibora()


@app.route('/', cache=False)
@app.route('/')
async def home():
return Response(str(time.time()).encode())
return Response(b'123')


if __name__ == '__main__':
Expand Down
16 changes: 4 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import platform
import re
import pathlib
import os
from setuptools import setup, Extension, find_packages

# Uvloop and ujson are notoriously problematic at Windows so they are skipped for Windows users.
# They still can install and benefit from it... it's just that Vibora doesnt make it mandatory.
dependencies = ['pendulum']
if platform.system().lower() == 'linux':
if os.environ.get('VIBORA_UVLOOP', 1) != '0':
dependencies.append('uvloop')
if os.environ.get('VIBORA_UJSON', 1) != '0':
dependencies.append('ujson')

# Loading version
here = pathlib.Path(__file__).parent
txt = (here / 'vibora' / '__version__.py').read_text()
Expand All @@ -35,7 +24,10 @@
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6'
],
install_requires=dependencies,
extras_require={
'dev': ['flake8', 'pytest', 'tox'],
'fast': ['ujson==1.35', 'uvloop==0.10.2']
},
ext_modules=[
Extension(
"vibora.parsers.parser",
Expand Down
10 changes: 4 additions & 6 deletions tests/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,12 @@ async def home():
async def test_default_expects_static_cache(self):
app = Vibora()

@app.route('/')
@app.route('/', cache=False)
async def home():
return JsonResponse({'now': time.time()})

client = app.test_client()
response1 = await client.get('/')
response2 = await client.get('/')
async with app.test_client() as client:
response1 = await client.get('/')
response2 = await client.get('/')

self.assertNotEqual(response1.content, response2.content)

client.close()
6 changes: 4 additions & 2 deletions tests/client/keep_alive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ async def test_connection_pool_recycling_connections(self):
v = Vibora()
address, port = '127.0.0.1', 65530
async with Session(prefix=f'http://{address}:{port}', timeout=3, keep_alive=True) as client:
v.run(host=address, port=port, block=False, verbose=False, necromancer=False, workers=1, debug=False)
v.run(host=address, port=port, block=False, necromancer=False, workers=1, debug=False,
startup_message=False)
self.assertEqual((await client.get('/')).status_code, 404)
v.clean_up()
wait_server_offline(address, port, timeout=30)
v.run(host=address, port=port, block=False, verbose=False, necromancer=False, workers=1, debug=False)
v.run(host=address, port=port, block=False, necromancer=False, workers=1, debug=False,
startup_message=False)
self.assertEqual((await client.get('/')).status_code, 404)
9 changes: 9 additions & 0 deletions tests/client/ssl_connections.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import ssl
import logging
from vibora.tests import TestSuite
from vibora import client


class TestSSLErrors(TestSuite):

def setUp(self):
# Python always warns about SSL errors but since where are forcing them to occur
# there is no reason to fill the testing console with these messages.
logging.disable(logging.CRITICAL)

def tearDown(self):
logging.disable(logging.NOTSET)

async def test_expired_ssl__expects_exception(self):
try:
await client.get('https://expired.badssl.com/')
Expand Down
4 changes: 2 additions & 2 deletions tests/responses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ujson
from unittest import TestCase
from vibora.responses import JsonResponse, Response
from vibora.cookies import Cookie
from vibora.utils import json


class AttributesTestCase(TestCase):
Expand All @@ -19,7 +19,7 @@ def test_json_response_attributes(self):
self.assertEqual(response.cookies, cookies)
self.assertEqual(response.headers['test'], headers['test'])
self.assertEqual(response.status_code, status_code)
self.assertEqual(response.content, ujson.dumps(content).encode('utf-8'))
self.assertEqual(response.content, json.dumps(content).encode('utf-8'))

def test_plain_response_attributes(self):
headers = {'server': 'Vibora'}
Expand Down
16 changes: 14 additions & 2 deletions tests/schemas/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from vibora.schemas import Schema, fields
from vibora.schemas.exceptions import ValidationError, InvalidSchema
from vibora.schemas.messages import Messages, EnglishLanguage
from vibora.schemas.validators import Length
from vibora.tests import TestSuite


Expand Down Expand Up @@ -119,9 +120,9 @@ class TestSchema(Schema):
except InvalidSchema as error:
self.assertDictEqual(error.errors, {
'field1': [{'error_code': Messages.MISSING_REQUIRED_FIELD,
'msg': EnglishLanguage[Messages.MISSING_REQUIRED_FIELD]({})}],
'msg': EnglishLanguage[Messages.MISSING_REQUIRED_FIELD]}],
'field3': [{'error_code': Messages.MISSING_REQUIRED_FIELD,
'msg': EnglishLanguage[Messages.MISSING_REQUIRED_FIELD]({})}]
'msg': EnglishLanguage[Messages.MISSING_REQUIRED_FIELD]}]
})

async def test_custom_language_with_new_error_code(self):
Expand All @@ -144,3 +145,14 @@ class TestSchema(Schema):
await TestSchema.load({'field1': 'test'}, language=new_language)
except InvalidSchema as error:
self.assertDictEqual(error.errors, {'field1': [{'error_code': 100, 'msg': 'Something custom.'}]})

async def test_schema_calling_builtin_validator(self):

class TestSchema(Schema):
field1: str = fields.String(validators=[Length(min=1)])

try:
await TestSchema.load({'field1': ''})
self.fail('Schema failed to call length validator.')
except InvalidSchema:
pass
21 changes: 0 additions & 21 deletions tests/templates/integration.py

This file was deleted.

26 changes: 26 additions & 0 deletions tests/templates/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from vibora.tests import TestSuite
from vibora.templates import TemplateEngine, Template


class RenderSuite(TestSuite):

def setUp(self):
self.engine = TemplateEngine()

async def test_empty_template_expects_empty_string(self):
template = Template('')
self.engine.add_template(template, ['test'])
self.engine.compile_templates(verbose=False)
self.assertEqual('', await self.engine.render('test'))

async def test_render_for_with_with_unpacked_variables(self):
template = Template('{% for a, b in [(1, 2)] %} {{ a }} + {{ b }} {% endfor %}')
self.engine.add_template(template, ['test'])
self.engine.compile_templates(verbose=False)
self.assertEqual(' 1 + 2 ', await self.engine.render('test'))

async def test_render_for_with_a_single_variable(self):
template = Template('{% for a in [(1, 2)] %} {{ a }} {% endfor %}')
self.engine.add_template(template, ['test'])
self.engine.compile_templates(verbose=False)
self.assertEqual(' (1, 2) ', await self.engine.render('test'))
25 changes: 5 additions & 20 deletions vibora/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .request import Request
from .blueprints import Blueprint
from .sessions import SessionEngine
from .router import Router, Route, RouterStrategy, RouteLimits
from .router import Router, RouterStrategy, RouteLimits
from .protocol import Connection
from .responses import Response
from .components import ComponentsEngine
Expand All @@ -20,7 +20,7 @@ class Application(Blueprint):

def __init__(self, template_dirs: List[str] = None, router_strategy=RouterStrategy.CLONE,
sessions_engine: SessionEngine=None, server_name: str = None, url_scheme: str = 'http',
static: StaticHandler=None, log: Callable = None,
static: StaticHandler=None, log_handler: Callable=None, access_logs: bool=None,
server_limits: ServerLimits=None, route_limits: RouteLimits=None,
request_class: Type[Request]=Request):
"""
Expand All @@ -31,7 +31,7 @@ def __init__(self, template_dirs: List[str] = None, router_strategy=RouterStrate
:param server_name:
:param url_scheme:
:param static:
:param log:
:param log_handler:
:param server_limits:
:param route_limits:
"""
Expand All @@ -48,7 +48,8 @@ def __init__(self, template_dirs: List[str] = None, router_strategy=RouterStrate
self.workers = []
self.components = ComponentsEngine()
self.loop = None
self.log = log
self.access_logs = access_logs
self.log_handler = log_handler
self.initialized = False
self.server_limits = server_limits or ServerLimits()
self.running = False
Expand Down Expand Up @@ -145,22 +146,6 @@ def clean_up(self):
process.terminate()
self.running = False

async def handle_exception(self, connection, exception, components, route: Route = None):
"""
:param components:
:param connection:
:param exception:
:param route:
:return:
"""
response = None
if route:
response = await route.parent.process_exception(exception, components)
if response is None:
response = await self.process_exception(exception, components)
response.send(connection)

def url_for(self, _name: str, _external=False, *args, **kwargs) -> str:
"""
Expand Down
36 changes: 20 additions & 16 deletions vibora/protocol/cprotocol.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,10 @@ cdef int current_time = time()
DEF PENDING_STATUS = 1
DEF RECEIVING_STATUS = 2
DEF PROCESSING_STATUS = 3

DEF EVENTS_BEFORE_ENDPOINT = 3
DEF EVENTS_AFTER_ENDPOINT = 4
DEF EVENTS_AFTER_RESPONSE_SENT = 5

DEF LOGGING_INFO = 20
DEF LOGGING_WARNING = 30
DEF LOGGING_ERROR = 40


cdef class Connection:

Expand Down Expand Up @@ -69,7 +64,7 @@ cdef class Connection:
self.keep_alive = app.server_limits.keep_alive_timeout > 0
self.request_class = self.app.request_class
self.router = self.app.router
self.log = self.app.log
self.log = self.app.log_handler
self.queue = self.stream.queue
self.write_buffer = app.server_limits.write_buffer

Expand Down Expand Up @@ -101,13 +96,6 @@ cdef class Connection:
Handle network flow after a response is sent. Must be called after each response.
:return: None.
"""
if self.log:
request = self.components.get(self.request_class)
msg = f'{self.client_ip()} - "{request.method.decode()} ' \
f'{request.parsed_url.path.decode()}" - {response.status_code} - ' \
f'{request.headers.get("user-agent")}'
self.log(msg, LOGGING_INFO)

self.status = PENDING_STATUS
if not self.keep_alive:
self.close()
Expand Down Expand Up @@ -204,7 +192,7 @@ cdef class Connection:
pass
except Exception as error:
self.components.ephemeral_index[type(error)] = error
task = self.app.handle_exception(self, error, self.components, route=route)
task = self.handle_exception(error, self.components, route=route)
self.loop.create_task(task)

#######################################################################
Expand All @@ -219,6 +207,7 @@ cdef class Connection:
:param upgrade:
:return:
"""
cdef Response response
cdef CacheEngine cache_engine
cdef dict ephemeral_components = self.components.ephemeral_index
Expand Down Expand Up @@ -310,7 +299,7 @@ cdef class Connection:
except HttpParserError as error:
self.pause_reading()
self.components.ephemeral_index[type(error)] = error
task = self.app.handle_exception(self, error, self.components)
task = self.handle_exception(error, self.components)
self.loop.create_task(task)
# self.close()

Expand Down Expand Up @@ -373,7 +362,7 @@ cdef class Connection:
self.current_task.cancel()
error = TimeoutError()
self.components.ephemeral_index[TimeoutError] = error
task = self.app.handle_exception(self, error, self.components)
task = self.handle_exception(error, self.components)
self.loop.create_task(task)

cpdef void stop(self):
Expand Down Expand Up @@ -432,6 +421,21 @@ cdef class Connection:
await sleep(0.5)
self.close()

async def handle_exception(self, object exception, object components, Route route = None):
"""
:param exception:
:param components:
:param route:
:return:
"""
cdef Response response = None
if route:
response = await route.parent.process_exception(exception, components)
if response is None:
response = await self.app.process_exception(exception, components)
response.send(self)


def update_current_time() -> None:
"""
Expand Down
Loading

0 comments on commit a03cfba

Please sign in to comment.