Browse Source

catch up w/ work, drop Klein for FastAPI, 3.8, and other minor

changes..
main
John-Mark Gurney 2 years ago
parent
commit
f90c3d16ba
9 changed files with 103 additions and 237 deletions
  1. +3
    -3
      ui/Makefile
  2. +0
    -198
      ui/kleintest.py
  3. +1
    -1
      ui/medashare/cli.py
  4. +11
    -0
      ui/medashare/config.py
  5. +0
    -0
      ui/medashare/mtree.py
  6. +39
    -26
      ui/medashare/server.py
  7. +3
    -0
      ui/medashare/tests.py
  8. +4
    -9
      ui/requirements.txt
  9. +42
    -0
      ui/setup.py

+ 3
- 3
ui/Makefile View File

@@ -1,9 +1,9 @@
VIRTUALENV?=virtualenv-3.7
MODULES=cli.py kleintest.py mtree.py server.py
VIRTUALENV?=python3.8 -m venv
MODULES=medashare

test: fixtures/sample.data.pasn1 fixtures/sample.persona.pasn1 fixtures/sample.mtree
(. ./p/bin/activate && \
(ls $(MODULES) | entr sh -c 'python3 -m coverage run -m unittest -f $(basename $(MODULES)) && coverage report -m --omit=p/\*'))
(find $(MODULES) -type f | entr sh -c 'python3 -m coverage run -m unittest $(MODULES).tests && coverage report -m --omit=p/\*'))

env:
$(VIRTUALENV) p


+ 0
- 198
ui/kleintest.py View File

@@ -1,198 +0,0 @@
#!/usr/bin/env python

from klein import Klein
from klein.interfaces import IKleinRequest
from twisted.internet.defer import Deferred
from twisted.trial import unittest
from twisted.web.http_headers import Headers
from zope.interface import implementer
from io import StringIO
from requests.structures import CaseInsensitiveDict

__all__ = [ 'FakeRequests', ]

# https://github.com/twisted/twisted/blob/twisted-19.7.0/src/twisted/web/http.py#L664
@implementer(IKleinRequest)
class FakeHTTPRequest(object):

code = 200

def __init__(self, meth, uri, data):

#self.requestHeaders = Headers()
self.responseHeaders = Headers()

self.content = StringIO(data)

self.path = uri
self.prepath = []
self.postpath = uri.split(b'/')
self.method = meth
self.notifications = []
self.finished = False

def setHeader(self, name, value):
self.responseHeaders.setRawHeaders(name, [value])

def setResponseCode(self, code, message=None):
self.code = code

def getRequestHostname(self):
return b''

def getHost(self):
return b''

def isSecure(self):
return False

def processingFailed(self, failure):
self.setResponseCode(500, 'Internal Server Error')

#print 'f:', `failure`
#print 'b:', failure.getTraceback()

def _cleanup(self):
for d in self.notifications:
d.callback(None)
self.notifications = []

def finish(self):
if self.finished: # pragma: no cover
warnings.warn('Warning! request.finish called twice.', stacklevel=2)

self.finished = True

self._cleanup()

def notifyFinish(self):
self.notifications.append(Deferred())
return self.notifications[-1]

class FakeRequestsResponse(object):
def __init__(self, req):
self._req = req
self._io = StringIO()
req.write = self.write
self.status_code = 500

def _finished(self, arg):
if arg is not None: # pragma: no cover
raise NotImplementedError('cannot handle exceptions yet')

self.status_code = self._req.code

self.text = self._io.getvalue()
self.headers = CaseInsensitiveDict((k.lower(), v[-1]) for k, v in self._req.responseHeaders.getAllRawHeaders())

def write(self, data):
self._io.write(data)

class FakeRequests(object):
'''This class wraps a Klein app into a calling interface that is similar
to the requests module for testing apps.

Example test:
```
app = Klein()

@app.route('/')
def home(request):
return 'hello'

class TestFakeRequests(unittest.TestCase):
def setUp(self):
self.requests = FakeRequests(app)

def test_basic(self):
r = self.requests.get('/')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.text, 'hello')
```
'''

def __init__(self, app):
'''Wrap the passed in app as if it was a server for a requests
like interface. The URLs expected will not be complete urls.'''

self._app = app
self._res = app.resource()

def _makerequest(self, method, url, data=''):
if url[0:1] != b'/':
raise ValueError('url must be absolute (start w/ a slash)')

req = FakeHTTPRequest('GET', url, data)
resp = FakeRequestsResponse(req)

req.notifyFinish().addBoth(resp._finished)

r = self._res.render(req)

return resp

def get(self, url):
'''Return a response for the passed in url.'''

print('ktg:', repr(url))
return self._makerequest('GET', url)

def put(self, url, data=''):
'''Make a put request to the provied URL w/ the body of data.'''

return self._makerequest('PUT', url, data)

class TestFakeRequests(unittest.TestCase):
def setUp(self):
self.putdata = []

app = Klein()

@app.route('/')
def home(request):
request.setHeader('x-testing', 'value')

return b'hello'

@app.route('/500')
def causeerror(request):
raise ValueError('random exception')

@app.route('/put')
def putreq(request):
self.putdata.append(request.content.read())

request.setResponseCode(201)
return ''

@app.route('/404')
def notfound(request):
request.setResponseCode(404)
return 'not found'

self.requests = FakeRequests(app)

def test_bad(self):
self.assertRaises(ValueError, self.requests.get, b'foobar')

def test_basic(self):
r = self.requests.get(b'/')
print(repr(r))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.text, 'hello')
self.assertEqual(r.headers['X-testing'], 'value')

r = self.requests.get('/404')
self.assertEqual(r.status_code, 404)

r = self.requests.get('/nonexistent')
self.assertEqual(r.status_code, 404)

r = self.requests.get('/500')
self.assertEqual(r.status_code, 500)

body = 'body'
r = self.requests.put('/put', data=body)
self.assertEqual(r.status_code, 201)
self.assertEqual(r.text, '')
self.assertEqual(''.join(self.putdata), body)

ui/cli.py → ui/medashare/cli.py View File

@@ -12,7 +12,7 @@ import copy
import datetime
import functools
import hashlib
import mock
from unittest import mock
import os.path
import pasn1
import shutil

+ 11
- 0
ui/medashare/config.py View File

@@ -0,0 +1,11 @@
from pydantic import BaseSettings, Field

import asyncio

__all__ = [ 'Settings' ]

class Settings(BaseSettings):
db_file: str = Field(description='path to SQLite3 database file')

class Config:
env_file = '.env'

ui/mtree.py → ui/medashare/mtree.py View File


ui/server.py → ui/medashare/server.py View File

@@ -2,28 +2,34 @@

# Notes:
# Python requests: https://2.python-requests.org/en/master/
# IRequest interface: https://twistedmatrix.com/documents/current/api/twisted.web.iweb.IRequest.html
# IResource interface: https://twistedmatrix.com/documents/current/api/twisted.web.resource.IResource.html
# Twisted TDD: https://twistedmatrix.com/documents/current/core/howto/trial.html
# Hypothesis: https://hypothesis.readthedocs.io/en/latest/
# Going Async from Flask to Twisted Klein: https://crossbario.com/blog/Going-Asynchronous-from-Flask-to-Twisted-Klein/
# Klein POST docs: https://klein.readthedocs.io/en/latest/examples/handlingpost.html

from klein import Klein
from kleintest import *
from twisted.trial import unittest
from twisted.web.iweb import IRequest
from cli import _asn1coder, Persona, MDBase, MetaData

from .cli import _asn1coder, Persona, MDBase, MetaData

from fastapi import FastAPI, APIRouter, Depends
from fastapi_restful.cbv import cbv
from functools import lru_cache
from unittest import mock

from . import config
import hashlib
import mock
import os.path
import shutil
import tempfile
import unittest
import uuid

router = APIRouter()

@lru_cache()
def get_settings(): # pragma: cover
return config.Settings()

defaultfile = 'mediaserver.store.pasn1'

@cbv(router)
class MEDAServer:
settings: config.Settings = Depends(get_settings)

def __init__(self, fname):
self._fname = fname
self._hashes = {}
@@ -42,8 +48,6 @@ class MEDAServer:
self._trustedkeys = {}
self._objstore = {}

app = Klein()

def addpubkey(self, pubkey):
persona = Persona.from_pubkey(pubkey)

@@ -58,14 +62,14 @@ class MEDAServer:
with open(self._fname, 'w') as fp:
fp.write(_asn1coder.dumps(obj))

@app.route('/lookup/<hash>')
@router.get('/lookup/<hash>')
def lookup(self, request, hash):
if hash in self._hashes:
return

request.setResponseCode(404)

@app.route('/obj/<id>')
@router.get('/obj/<id>')
def obj_lookup(self, request, id):
try:
id = uuid.UUID(id)
@@ -84,7 +88,7 @@ class MEDAServer:
except AttributeError:
pass

@app.route('/store')
@router.post('/store')
def storeobj(self, request):
try:
obj = MDBase.decode(request.content.read())
@@ -104,6 +108,12 @@ class MEDAServer:
except Exception:
request.setResponseCode(401)

def getApp():
app = FastAPI()
app.include_router(router)

return app

# twistd support
#medaserver = MEDAServer()
#resource = medaserver.app.resource
@@ -132,17 +142,20 @@ def main():
if __name__ == '__main__': # pragma: no cover
main()

class _BaseServerTest(unittest.TestCase):
def setUp(self):
class _BaseServerTest(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.app = getApp()

# setup test database
self.dbtempfile = tempfile.NamedTemporaryFile()

# setup settings
self.settings = config.Settings(db_file=self.dbtempfile.name,
)

d = os.path.realpath(tempfile.mkdtemp())
self.basetempdir = d
self.medaserverfile = os.path.join(self.basetempdir, 'serverstore.pasn1')
self.medaserver = MEDAServer(self.medaserverfile)
self.requests = FakeRequests(self.medaserver.app)

def tearDown(self):
shutil.rmtree(self.basetempdir)
self.basetempdir = None

class _TestCases(_BaseServerTest):
def test_objlookup(self):

+ 3
- 0
ui/medashare/tests.py View File

@@ -0,0 +1,3 @@
from .cli import _TestCononicalCoder, _TestCases
from .mtree import Test
from .server import _TestCases, _TestPostConfig

+ 4
- 9
ui/requirements.txt View File

@@ -1,9 +1,4 @@
urwid
-e git+https://www.funkthat.com/gitea/jmg/pasn1.git@01d8efffd7bc3037dcb894ea44dbe959035948c6#egg=pasn1
coverage
mock
klein
cryptography
base58
# for kleintest
requests
# use setup.py for dependancy info
-e .

-e .[dev]

+ 42
- 0
ui/setup.py View File

@@ -0,0 +1,42 @@

# python setup.py --dry-run --verbose install

import os.path
from setuptools import setup, find_packages

from distutils.core import setup

setup(
name='medashare',
version='0.1.0',
author='John-Mark Gurney',
author_email='jmg@funkthat.com',
packages=find_packages(),
#url='',
license='BSD',
description='File Metadata sharing, query and storing utility.',
#download_url='',
long_description=open('README.md').read(),
python_requires='>=3.8',
install_requires=[
'base58',
'cryptography',
'databases[sqlite]',
'fastapi',
'fastapi_restful',
'httpx',
'hypercorn', # option, for server only?
'orm',
'pasn1 @ git+https://www.funkthat.com/gitea/jmg/pasn1.git@01d8efffd7bc3037dcb894ea44dbe959035948c6#egg=pasn1',
'pydantic[dotenv]',
],
extras_require = {
# requests needed for fastpi.testclient.TestClient
'dev': [ 'coverage', 'requests' ],
},
entry_points={
'console_scripts': [
'medashare = medashare.__main__:main',
]
}
)

Loading…
Cancel
Save