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 test: fixtures/sample.data.pasn1 fixtures/sample.persona.pasn1 fixtures/sample.mtree
(. ./p/bin/activate && \ (. ./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: env:
$(VIRTUALENV) p $(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 datetime
import functools import functools
import hashlib import hashlib
import mock
from unittest import mock
import os.path import os.path
import pasn1 import pasn1
import shutil 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: # Notes:
# Python requests: https://2.python-requests.org/en/master/ # 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 hashlib
import mock
import os.path import os.path
import shutil import shutil
import tempfile import tempfile
import unittest
import uuid import uuid


router = APIRouter()

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

defaultfile = 'mediaserver.store.pasn1' defaultfile = 'mediaserver.store.pasn1'

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

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


app = Klein()

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


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


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


request.setResponseCode(404) request.setResponseCode(404)


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


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


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

return app

# twistd support # twistd support
#medaserver = MEDAServer() #medaserver = MEDAServer()
#resource = medaserver.app.resource #resource = medaserver.app.resource
@@ -132,17 +142,20 @@ def main():
if __name__ == '__main__': # pragma: no cover if __name__ == '__main__': # pragma: no cover
main() 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()) d = os.path.realpath(tempfile.mkdtemp())
self.basetempdir = d self.basetempdir = d
self.medaserverfile = os.path.join(self.basetempdir, 'serverstore.pasn1') 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): class _TestCases(_BaseServerTest):
def test_objlookup(self): 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