Initial Commit
This commit is contained in:
commit
170a2ee6ec
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
/.tox
|
||||||
|
/env*/
|
||||||
|
.idea/
|
||||||
|
*.egg-info/
|
4
CHANGES.txt
Normal file
4
CHANGES.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
- Initial version.
|
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
include *.txt *.ini *.cfg *.rst
|
||||||
|
recursive-include randy *.ico *.txt
|
51
README.txt
Normal file
51
README.txt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
randy
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- Change directory into your newly created project.
|
||||||
|
|
||||||
|
cd randy
|
||||||
|
|
||||||
|
- Create a Python virtual environment.
|
||||||
|
|
||||||
|
python3 -m venv env
|
||||||
|
|
||||||
|
- Upgrade packaging tools.
|
||||||
|
|
||||||
|
env/bin/pip install --upgrade pip setuptools
|
||||||
|
|
||||||
|
- Install the project in editable mode.
|
||||||
|
|
||||||
|
env/bin/pip install -e
|
||||||
|
|
||||||
|
- Configure the database.
|
||||||
|
|
||||||
|
env/bin/initialize_randy_db development.ini
|
||||||
|
|
||||||
|
- Once configured, scan the files by running the script.
|
||||||
|
|
||||||
|
env/bin/scan development.ini
|
||||||
|
|
||||||
|
- Run your project.
|
||||||
|
|
||||||
|
env/bin/pserve development.ini
|
||||||
|
|
||||||
|
|
||||||
|
Picture Model
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- Uri:
|
||||||
|
Format:
|
||||||
|
name_of_directory_added:path/name
|
||||||
|
eg.
|
||||||
|
Pictures:Vanya/Italy/DSC001.jpg
|
||||||
|
|
||||||
|
- Hash
|
||||||
|
Format:
|
||||||
|
default is sha-2
|
||||||
|
algorithm:hash
|
||||||
|
|
||||||
|
- Weight
|
||||||
|
Default value is 4096. Every thumbs up double value, every thumbs down will halve it.
|
65
development.ini
Normal file
65
development.ini
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
###
|
||||||
|
# app configuration
|
||||||
|
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:randy
|
||||||
|
|
||||||
|
pyramid.reload_templates = true
|
||||||
|
pyramid.debug_authorization = false
|
||||||
|
pyramid.debug_notfound = false
|
||||||
|
pyramid.debug_routematch = false
|
||||||
|
pyramid.default_locale_name = en
|
||||||
|
|
||||||
|
sqlalchemy.url = postgresql://postgres:123456@localhost:5432/randy
|
||||||
|
|
||||||
|
directories = Pen Drive:c:\Users\tanshu\Desktop\Pen Drive\
|
||||||
|
Pictures:c:\Users\tanshu\Pictures\
|
||||||
|
###
|
||||||
|
# wsgi server configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:waitress#main
|
||||||
|
listen = 127.0.0.1:5000 [::1]:5000
|
||||||
|
|
||||||
|
###
|
||||||
|
# logging configuration
|
||||||
|
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root, randy, sqlalchemy
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = INFO
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_randy]
|
||||||
|
level = DEBUG
|
||||||
|
handlers =
|
||||||
|
qualname = randy
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
# "level = INFO" logs SQL queries.
|
||||||
|
# "level = DEBUG" logs SQL queries and results.
|
||||||
|
# "level = WARN" logs neither. (Recommended for production systems.)
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
|
4
install.sh
Normal file
4
install.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
python3 -m venv env
|
||||||
|
./env/bin/pip install --upgrade pip
|
||||||
|
./env/bin/pip install -e .
|
||||||
|
|
66
production.ini
Normal file
66
production.ini
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
###
|
||||||
|
# app configuration
|
||||||
|
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:randy
|
||||||
|
|
||||||
|
pyramid.reload_templates = false
|
||||||
|
pyramid.debug_authorization = false
|
||||||
|
pyramid.debug_notfound = false
|
||||||
|
pyramid.debug_routematch = false
|
||||||
|
pyramid.default_locale_name = en
|
||||||
|
|
||||||
|
sqlalchemy.url = postgresql://postgres:123456@localhost:5432/randy
|
||||||
|
|
||||||
|
directories = Pictures:/home/user/Pictures/
|
||||||
|
Others:c:\Users\user\Pictures\
|
||||||
|
|
||||||
|
###
|
||||||
|
# wsgi server configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:waitress#main
|
||||||
|
listen = *:5000
|
||||||
|
|
||||||
|
###
|
||||||
|
# logging configuration
|
||||||
|
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
|
||||||
|
###
|
||||||
|
|
||||||
|
[loggers]
|
||||||
|
keys = root, randy, sqlalchemy
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_randy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = randy
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
# "level = INFO" logs SQL queries.
|
||||||
|
# "level = DEBUG" logs SQL queries and results.
|
||||||
|
# "level = WARN" logs neither. (Recommended for production systems.)
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
|
15
randy.service
Normal file
15
randy.service
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Tanshu's Random Image Server
|
||||||
|
After=multi-user.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
ExecStart=/home/tanshu/Programming/randy/env/bin/pserve production.ini
|
||||||
|
User=tanshu
|
||||||
|
WorkingDirectory=/home/tanshu/Programming/randy
|
||||||
|
ExecStop=/usr/bin/pkill -f /home/tanshu/Programming/randy/env/bin/pserve
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
12
randy/__init__.py
Normal file
12
randy/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from pyramid.config import Configurator
|
||||||
|
|
||||||
|
|
||||||
|
def main(global_config, **settings):
|
||||||
|
""" This function returns a Pyramid WSGI application.
|
||||||
|
"""
|
||||||
|
config = Configurator(settings=settings)
|
||||||
|
config.include('.models')
|
||||||
|
config.include('.routes')
|
||||||
|
config.scan()
|
||||||
|
return config.make_wsgi_app()
|
||||||
|
|
73
randy/models/__init__.py
Normal file
73
randy/models/__init__.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.orm import configure_mappers
|
||||||
|
import zope.sqlalchemy
|
||||||
|
|
||||||
|
# import or define all models here to ensure they are attached to the
|
||||||
|
# Base.metadata prior to any initialization routines
|
||||||
|
from .picture import Picture # flake8: noqa
|
||||||
|
|
||||||
|
# run configure_mappers after defining all of the models to ensure
|
||||||
|
# all relationships can be setup
|
||||||
|
configure_mappers()
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine(settings, prefix='sqlalchemy.'):
|
||||||
|
return engine_from_config(settings, prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_factory(engine):
|
||||||
|
factory = sessionmaker()
|
||||||
|
factory.configure(bind=engine)
|
||||||
|
return factory
|
||||||
|
|
||||||
|
|
||||||
|
def get_tm_session(session_factory, transaction_manager):
|
||||||
|
"""
|
||||||
|
Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
|
||||||
|
|
||||||
|
This function will hook the session to the transaction manager which
|
||||||
|
will take care of committing any changes.
|
||||||
|
|
||||||
|
- When using pyramid_tm it will automatically be committed or aborted
|
||||||
|
depending on whether an exception is raised.
|
||||||
|
|
||||||
|
- When using scripts you should wrap the session in a manager yourself.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
import transaction
|
||||||
|
|
||||||
|
engine = get_engine(settings)
|
||||||
|
session_factory = get_session_factory(engine)
|
||||||
|
with transaction.manager:
|
||||||
|
dbsession = get_tm_session(session_factory, transaction.manager)
|
||||||
|
|
||||||
|
"""
|
||||||
|
dbsession = session_factory()
|
||||||
|
zope.sqlalchemy.register(
|
||||||
|
dbsession, transaction_manager=transaction_manager)
|
||||||
|
return dbsession
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
"""
|
||||||
|
Initialize the model for a Pyramid app.
|
||||||
|
|
||||||
|
Activate this setup using ``config.include('randy.models')``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
settings = config.get_settings()
|
||||||
|
|
||||||
|
# use pyramid_tm to hook the transaction lifecycle to the request
|
||||||
|
config.include('pyramid_tm')
|
||||||
|
|
||||||
|
session_factory = get_session_factory(get_engine(settings))
|
||||||
|
config.registry['dbsession_factory'] = session_factory
|
||||||
|
|
||||||
|
# make request.dbsession available for use in Pyramid
|
||||||
|
config.add_request_method(
|
||||||
|
# r.tm is the transaction manager used by pyramid_tm
|
||||||
|
lambda r: get_tm_session(session_factory, r.tm),
|
||||||
|
'dbsession',
|
||||||
|
reify=True
|
||||||
|
)
|
57
randy/models/guidtype.py
Normal file
57
randy/models/guidtype.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import uuid
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.dialects.sqlite import BLOB
|
||||||
|
from sqlalchemy.types import TypeDecorator, CHAR, Binary
|
||||||
|
|
||||||
|
|
||||||
|
class GUID(TypeDecorator):
|
||||||
|
"""Platform-independent GUID type.
|
||||||
|
|
||||||
|
Uses Postgresql's UUID type, otherwise uses
|
||||||
|
CHAR(32), storing as stringified hex values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
impl = Binary
|
||||||
|
|
||||||
|
# if dialect.value == 'postgresql':
|
||||||
|
# impl = CHAR
|
||||||
|
# elif dialect.value == 'mysql':
|
||||||
|
# impl = MSBinary
|
||||||
|
# elif dialect.valie == 'sqlite':
|
||||||
|
# impl = Binary
|
||||||
|
# else:
|
||||||
|
# impl = Binary
|
||||||
|
|
||||||
|
def load_dialect_impl(self, dialect):
|
||||||
|
if dialect.name == 'postgresql':
|
||||||
|
return dialect.type_descriptor(UUID())
|
||||||
|
elif dialect.name == 'sqlite':
|
||||||
|
return dialect.type_descriptor(BLOB())
|
||||||
|
else:
|
||||||
|
return dialect.type_descriptor(CHAR(32))
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
elif dialect.name == 'postgresql':
|
||||||
|
return str(value)
|
||||||
|
elif not isinstance(value, uuid.UUID):
|
||||||
|
raise ValueError('value %s is not a valid uuid.UUID' % value)
|
||||||
|
else:
|
||||||
|
return value.bytes
|
||||||
|
# if not isinstance(value, uuid.UUID):
|
||||||
|
# return "%.32x" % uuid.UUID(value)
|
||||||
|
# else:
|
||||||
|
# # hexstring
|
||||||
|
# return "%.32x" % value
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect=None):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
return uuid.UUID(bytes=value)
|
||||||
|
else:
|
||||||
|
return uuid.UUID(value)
|
||||||
|
|
||||||
|
def is_mutable(self):
|
||||||
|
return False
|
16
randy/models/meta.py
Normal file
16
randy/models/meta.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.schema import MetaData
|
||||||
|
|
||||||
|
# Recommended naming convention used by Alembic, as various different database
|
||||||
|
# providers will autogenerate vastly different names making migrations more
|
||||||
|
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
|
||||||
|
NAMING_CONVENTION = {
|
||||||
|
"ix": 'ix_%(column_0_label)s',
|
||||||
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||||
|
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
||||||
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||||
|
"pk": "pk_%(table_name)s"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = MetaData(naming_convention=NAMING_CONVENTION)
|
||||||
|
Base = declarative_base(metadata=metadata)
|
46
randy/models/picture.py
Normal file
46
randy/models/picture.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
Integer,
|
||||||
|
Unicode,
|
||||||
|
DateTime,
|
||||||
|
SmallInteger,
|
||||||
|
UniqueConstraint
|
||||||
|
)
|
||||||
|
from sqlalchemy.dialects.postgresql import JSON
|
||||||
|
|
||||||
|
from randy.models.meta import Base
|
||||||
|
from randy.models.guidtype import GUID
|
||||||
|
|
||||||
|
|
||||||
|
class Picture(Base):
|
||||||
|
__tablename__ = 'pictures'
|
||||||
|
__table_args__ = (UniqueConstraint('modified_time', 'uri', 'hash', 'size'), )
|
||||||
|
|
||||||
|
id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
|
||||||
|
uri = Column('uri', Unicode(255), unique=True)
|
||||||
|
weight = Column('weight', SmallInteger)
|
||||||
|
file_hash = Column('hash', Unicode(255))
|
||||||
|
exif = Column('exif', JSON)
|
||||||
|
cloud_vision = Column('cloud_vision', JSON)
|
||||||
|
size = Column('size', Integer)
|
||||||
|
modified_time = Column('modified_time', DateTime)
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
uri=None,
|
||||||
|
weight=4096,
|
||||||
|
file_hash=None,
|
||||||
|
exif=None,
|
||||||
|
cloud_vision=None,
|
||||||
|
size=None,
|
||||||
|
modified_date=None,
|
||||||
|
picture_id=None):
|
||||||
|
self.uri = uri
|
||||||
|
self.weight = weight
|
||||||
|
self.file_hash = file_hash
|
||||||
|
self.exif = exif
|
||||||
|
self.cloud_vision = cloud_vision
|
||||||
|
self.size = size
|
||||||
|
self.modified_date = modified_date
|
||||||
|
self.id = picture_id
|
3
randy/routes.py
Normal file
3
randy/routes.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
def includeme(config):
|
||||||
|
config.add_route('random', '/random')
|
||||||
|
config.add_route('id', '/{id}')
|
1
randy/scripts/__init__.py
Normal file
1
randy/scripts/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# package
|
36
randy/scripts/initializedb.py
Normal file
36
randy/scripts/initializedb.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import transaction
|
||||||
|
|
||||||
|
from pyramid.paster import (
|
||||||
|
get_appsettings,
|
||||||
|
setup_logging,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pyramid.scripts.common import parse_vars
|
||||||
|
|
||||||
|
from ..models.meta import Base
|
||||||
|
from ..models import (
|
||||||
|
get_engine,
|
||||||
|
get_session_factory,
|
||||||
|
get_tm_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def usage(argv):
|
||||||
|
cmd = os.path.basename(argv[0])
|
||||||
|
print('usage: %s <config_uri> [var=value]\n'
|
||||||
|
'(example: "%s development.ini")' % (cmd, cmd))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv) < 2:
|
||||||
|
usage(argv)
|
||||||
|
config_uri = argv[1]
|
||||||
|
options = parse_vars(argv[2:])
|
||||||
|
setup_logging(config_uri)
|
||||||
|
settings = get_appsettings(config_uri, options=options)
|
||||||
|
|
||||||
|
engine = get_engine(settings)
|
||||||
|
Base.metadata.create_all(engine)
|
86
randy/scripts/scan.py
Normal file
86
randy/scripts/scan.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import glob
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import transaction
|
||||||
|
from pyramid.paster import (
|
||||||
|
get_appsettings,
|
||||||
|
setup_logging,
|
||||||
|
)
|
||||||
|
from pyramid.scripts.common import parse_vars
|
||||||
|
from ..models import (
|
||||||
|
get_engine,
|
||||||
|
get_session_factory,
|
||||||
|
get_tm_session,
|
||||||
|
Picture
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def usage(argv):
|
||||||
|
cmd = os.path.basename(argv[0])
|
||||||
|
print('usage: %s <config_uri> [var=value]\n'
|
||||||
|
'(example: "%s development.ini")' % (cmd, cmd))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv) < 2:
|
||||||
|
usage(argv)
|
||||||
|
config_uri = argv[1]
|
||||||
|
options = parse_vars(argv[2:])
|
||||||
|
setup_logging(config_uri)
|
||||||
|
settings = get_appsettings(config_uri, options=options)
|
||||||
|
|
||||||
|
engine = get_engine(settings)
|
||||||
|
session_factory = get_session_factory(engine)
|
||||||
|
with transaction.manager:
|
||||||
|
dbsession = get_tm_session(session_factory, transaction.manager)
|
||||||
|
scan(settings['directories'].splitlines(), dbsession)
|
||||||
|
|
||||||
|
|
||||||
|
def scan(directories, dbsession):
|
||||||
|
for item in directories:
|
||||||
|
name, path = item.split(':', 1)
|
||||||
|
getFiles(name, path, dbsession)
|
||||||
|
|
||||||
|
|
||||||
|
def getFiles(name, path, dbsession):
|
||||||
|
files = glob.glob(path + '**/*.jpg', recursive=True)
|
||||||
|
for file in files:
|
||||||
|
uri = name + ':' + file[len(path):]
|
||||||
|
|
||||||
|
size = os.path.getsize(file)
|
||||||
|
modified_time = time.gmtime(os.path.getmtime(file))
|
||||||
|
old = dbsession.query(Picture).filter(Picture.uri == uri).first()
|
||||||
|
# if old is not None and old.size == size and old.modified_time == modified_time:
|
||||||
|
if old is not None:
|
||||||
|
print('Old file with ' + old.file_hash + ' exists!')
|
||||||
|
continue
|
||||||
|
file_hash = getHash(file)
|
||||||
|
# if old is not None:
|
||||||
|
# old.size = size
|
||||||
|
# old.modified_time = modified_time
|
||||||
|
# print('updated old', old.modified_time, 'with', modified_time)
|
||||||
|
# old.file_hash = file_hash
|
||||||
|
# print('Updated file with ' + file_hash)
|
||||||
|
# else:
|
||||||
|
picture = Picture(uri, 4096, file_hash, {}, {}, size, modified_time)
|
||||||
|
dbsession.add(picture)
|
||||||
|
print('Added file with ' + file_hash)
|
||||||
|
|
||||||
|
|
||||||
|
def getHash(file):
|
||||||
|
# BUF_SIZE is totally arbitrary, change for your app!
|
||||||
|
BUF_SIZE = 1 * 1024 * 1024 # lets read stuff in 64kb chunks!
|
||||||
|
sha256 = hashlib.sha256()
|
||||||
|
|
||||||
|
with open(file, 'rb') as f:
|
||||||
|
while True:
|
||||||
|
data = f.read(BUF_SIZE)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
sha256.update(data)
|
||||||
|
|
||||||
|
return "SHA256:{0}".format(sha256.hexdigest())
|
31
randy/views.py
Normal file
31
randy/views.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from pyramid.response import Response, FileResponse
|
||||||
|
from pyramid.view import view_config
|
||||||
|
from sqlalchemy.exc import DBAPIError
|
||||||
|
from .models import Picture
|
||||||
|
|
||||||
|
|
||||||
|
@view_config(route_name='random')
|
||||||
|
def random_view(request):
|
||||||
|
try:
|
||||||
|
chosen = weighted_choice(request.dbsession.query(Picture.id, Picture.weight).all())
|
||||||
|
item = request.dbsession.query(Picture).filter(Picture.id == chosen).first()
|
||||||
|
except DBAPIError:
|
||||||
|
return Response("", content_type='text/plain', status=500)
|
||||||
|
directories = request.registry.settings['directories'].splitlines()
|
||||||
|
repo, path = item.uri.split(':', 1)
|
||||||
|
location = [i for i in directories if i.startswith(repo)][0]
|
||||||
|
file = location[len(repo) + 1:] + path
|
||||||
|
return FileResponse(file, request=request)
|
||||||
|
|
||||||
|
|
||||||
|
def weighted_choice(choices):
|
||||||
|
total = sum(w for c, w in choices)
|
||||||
|
r = random.uniform(0, total)
|
||||||
|
upto = 0
|
||||||
|
for c, w in choices:
|
||||||
|
upto += w
|
||||||
|
if upto >= r:
|
||||||
|
return c
|
||||||
|
assert False, "Shouldn't get here"
|
49
setup.py
Normal file
49
setup.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
with open(os.path.join(here, 'README.txt')) as f:
|
||||||
|
README = f.read()
|
||||||
|
with open(os.path.join(here, 'CHANGES.txt')) as f:
|
||||||
|
CHANGES = f.read()
|
||||||
|
|
||||||
|
requires = [
|
||||||
|
'pyramid',
|
||||||
|
'pyramid_tm',
|
||||||
|
'SQLAlchemy',
|
||||||
|
'psycopg2',
|
||||||
|
'transaction',
|
||||||
|
'zope.sqlalchemy',
|
||||||
|
'waitress',
|
||||||
|
]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='randy',
|
||||||
|
version='0.0',
|
||||||
|
description='randy',
|
||||||
|
long_description=README + '\n\n' + CHANGES,
|
||||||
|
classifiers=[
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Framework :: Pyramid',
|
||||||
|
'Topic :: Internet :: WWW/HTTP',
|
||||||
|
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
|
||||||
|
],
|
||||||
|
author='',
|
||||||
|
author_email='',
|
||||||
|
url='',
|
||||||
|
keywords='web pyramid pylons',
|
||||||
|
packages=find_packages(),
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
install_requires=requires,
|
||||||
|
entry_points={
|
||||||
|
'paste.app_factory': [
|
||||||
|
'main = randy:main',
|
||||||
|
],
|
||||||
|
'console_scripts': [
|
||||||
|
'initialize_db = randy.scripts.initializedb:main',
|
||||||
|
'scan = randy.scripts.scan:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user