many changes
This commit is contained in:
parent
72a3073a5b
commit
1206aaab74
|
@ -5,7 +5,7 @@ from functools import partial
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from .dotdict import DotDict
|
from .dotdict import DotDict
|
||||||
from .misc import DateString, Url
|
from .misc import DateString, Url, boolean
|
||||||
|
|
||||||
|
|
||||||
pubstr = 'https://www.w3.org/ns/activitystreams#Public'
|
pubstr = 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
@ -233,7 +233,7 @@ class Object(DotDict):
|
||||||
data.attachment.append(PropertyValue(key, value))
|
data.attachment.append(PropertyValue(key, value))
|
||||||
|
|
||||||
if kwargs.get('avatar_url'):
|
if kwargs.get('avatar_url'):
|
||||||
data.icon = Object.new_icon(kwargs.get('avatar_url'), kwargs.get('avatar_type'))
|
data.icon = Object.new_image(kwargs.get('avatar_url'), kwargs.get('avatar_type'))
|
||||||
|
|
||||||
# need to add data when "full" is true
|
# need to add data when "full" is true
|
||||||
if not full:
|
if not full:
|
||||||
|
@ -247,7 +247,6 @@ class Object(DotDict):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_activity(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
def new_activity(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
||||||
assert type in activity_types
|
assert type in activity_types
|
||||||
|
@ -274,12 +273,49 @@ class Object(DotDict):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class Media(Object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_image(cls, url, type=None):
|
def new(cls, type, url, mime=None):
|
||||||
return cls({
|
return cls(
|
||||||
'type': 'Image',
|
type = 'Image',
|
||||||
'mediaType': type or mimetypes.guess_type(url)[0] or 'image/png',
|
mediaType = mime or mimetypes.guess_type(url)[0] or 'image/png',
|
||||||
'url': url
|
url = url
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_image(cls, url, mime=None):
|
||||||
|
return cls.new('Image', url, mime)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_video(cls, url, mime=None):
|
||||||
|
return cls.new('Video', url, mime)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_audio(cls, url, mime=None):
|
||||||
|
return cls.new('Audio', url, mime)
|
||||||
|
|
||||||
|
|
||||||
|
class Activity(DotDict):
|
||||||
|
@property
|
||||||
|
def privacy_level(self):
|
||||||
|
return parse_privacy_level(self.get('to', []), self.get('cc', []))
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_activity(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
||||||
|
assert type in activity_types
|
||||||
|
|
||||||
|
cls({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': id,
|
||||||
|
'object': object,
|
||||||
|
'type': type,
|
||||||
|
'to': to,
|
||||||
|
'cc': cc,
|
||||||
|
'actor': actor_src
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -319,3 +355,91 @@ class PropertyValue(DotDict):
|
||||||
def set_pair(self, key, value):
|
def set_pair(self, key, value):
|
||||||
self.name = key
|
self.name = key
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
class Nodeinfo(DotDict):
|
||||||
|
@classmethod
|
||||||
|
def new_20(cls, name, version, **metadata):
|
||||||
|
return cls.new(name, version, '2.0', **metadata)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_21(cls, name, version, **metadata):
|
||||||
|
return cls.new(name, version, '2.1', **metadata)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, name, version, niversion='2.1', **kwargs):
|
||||||
|
print(name, version, niversion)
|
||||||
|
assert niversion in ['2.0', '2.1']
|
||||||
|
|
||||||
|
open_regs = boolean(kwargs.pop('open_regs', True))
|
||||||
|
posts = int(kwargs.pop('posts', 0))
|
||||||
|
users = int(kwargs.pop('users', 0))
|
||||||
|
users_halfyear = int(kwargs.pop('halfyear', 0))
|
||||||
|
users_month = int(kwargs.pop('month', 0))
|
||||||
|
comments = int(kwargs.pop('comments', 0))
|
||||||
|
repository = kwargs.pop('repository', None)
|
||||||
|
homepage = kwargs.pop('homepage', None)
|
||||||
|
|
||||||
|
data = cls(
|
||||||
|
version = niversion,
|
||||||
|
openRegistrations = open_regs,
|
||||||
|
software = {
|
||||||
|
'name': name.lower().replace(' ', '-'),
|
||||||
|
'version': version
|
||||||
|
},
|
||||||
|
usage = {
|
||||||
|
'users': {
|
||||||
|
'total': users
|
||||||
|
}
|
||||||
|
},
|
||||||
|
protocols = [
|
||||||
|
'activitypub'
|
||||||
|
],
|
||||||
|
services = {
|
||||||
|
'inbound': kwargs.pop('inbound', []),
|
||||||
|
'outbound': kwargs.pop('outbound', [])
|
||||||
|
},
|
||||||
|
metadata = kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.version == '2.1':
|
||||||
|
if repository:
|
||||||
|
data.software.repository = repository
|
||||||
|
|
||||||
|
if homepage:
|
||||||
|
data.software.homepage = homepage
|
||||||
|
|
||||||
|
if users_halfyear:
|
||||||
|
data.users.activeHalfyear = halfyear
|
||||||
|
|
||||||
|
if users_month:
|
||||||
|
data.users.activeMonth = month
|
||||||
|
|
||||||
|
if posts:
|
||||||
|
data.usage.localPosts = posts
|
||||||
|
|
||||||
|
if comments:
|
||||||
|
data.usage.localComments = comments
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class WellknownNodeinfo(DotDict):
|
||||||
|
@classmethod
|
||||||
|
def new(cls, path, version='2.1'):
|
||||||
|
data = cls(links=[])
|
||||||
|
data.append(path, version)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def append(self, path, version='2.1'):
|
||||||
|
assert version in ['2.0', '2.1']
|
||||||
|
|
||||||
|
self.links.append({
|
||||||
|
'rel': f'http://nodeinfo.dispora.software/ns/schema/{version}',
|
||||||
|
'href': path
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ from ..misc import signal_handler
|
||||||
from ..path import Path
|
from ..path import Path
|
||||||
from ..template import Template
|
from ..template import Template
|
||||||
|
|
||||||
|
try: from ..sql import Database
|
||||||
|
except ImportError: Database = None
|
||||||
|
|
||||||
frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
|
frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
|
||||||
|
|
||||||
|
@ -24,8 +26,9 @@ frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
|
||||||
class ApplicationBase:
|
class ApplicationBase:
|
||||||
ctx = DotDict()
|
ctx = DotDict()
|
||||||
|
|
||||||
def __init__(self, views=[], middleware=[], **kwargs):
|
def __init__(self, views=[], middleware=[], dbtype=None, dbargs={}, **kwargs):
|
||||||
self.cfg = Config(**kwargs)
|
self.cfg = Config(**kwargs)
|
||||||
|
self.db = None
|
||||||
self.router = Router(trim_last_slash=True)
|
self.router = Router(trim_last_slash=True)
|
||||||
self.middleware = DotDict({'request': [], 'response': []})
|
self.middleware = DotDict({'request': [], 'response': []})
|
||||||
|
|
||||||
|
@ -35,6 +38,12 @@ class ApplicationBase:
|
||||||
for mw in middleware:
|
for mw in middleware:
|
||||||
self.add_middleware(mw)
|
self.add_middleware(mw)
|
||||||
|
|
||||||
|
if dbtype or dbargs:
|
||||||
|
if not Database:
|
||||||
|
raise NotImplementedError('Failed to import SQL database class')
|
||||||
|
|
||||||
|
self.db = Database(dbtype, **dbargs)
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.ctx[key]
|
return self.ctx[key]
|
||||||
|
@ -210,7 +219,7 @@ class Application(ApplicationBase):
|
||||||
task.cancel()
|
task.cancel()
|
||||||
self._tasks.remove(task)
|
self._tasks.remove(task)
|
||||||
|
|
||||||
signal_handler()
|
signal_handler(None)
|
||||||
|
|
||||||
|
|
||||||
def start(self, *tasks, log=True):
|
def start(self, *tasks, log=True):
|
||||||
|
|
107
izzylib/misc.py
107
izzylib/misc.py
|
@ -1,4 +1,16 @@
|
||||||
import grp, hashlib, os, platform, random, shlex, signal, socket, statistics, string, time, timeit
|
import argparse
|
||||||
|
import grp
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import shlex
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import statistics
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
import timeit
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from getpass import getpass, getuser
|
from getpass import getpass, getuser
|
||||||
|
@ -30,6 +42,7 @@ __all__ = [
|
||||||
'time_function_pprint',
|
'time_function_pprint',
|
||||||
'timestamp',
|
'timestamp',
|
||||||
'var_name',
|
'var_name',
|
||||||
|
'ArgParser',
|
||||||
'DateString',
|
'DateString',
|
||||||
'Url'
|
'Url'
|
||||||
]
|
]
|
||||||
|
@ -42,13 +55,37 @@ datetime_formats = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def app_data_dirs(author, name):
|
||||||
|
if platform.system() == 'Linux':
|
||||||
|
config = Path('~/.config')
|
||||||
|
cache = Path('~/.cache')
|
||||||
|
|
||||||
|
elif platform.system() == 'Darwin':
|
||||||
|
config = Path('~/Application Support')
|
||||||
|
cache = Path('~/Library/Caches')
|
||||||
|
|
||||||
|
elif platform.system() == 'Windows':
|
||||||
|
return DotDict(
|
||||||
|
config = Path('~/Application Data/Local Settings').join(author).join(name),
|
||||||
|
cache = Path('~/Application Data').join(author).join(name).join('Cache')
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise TypeError(f'Unknown system: {platform.system()}')
|
||||||
|
|
||||||
|
return DotDict(
|
||||||
|
config = config.join(author).join(name),
|
||||||
|
cache = cache.join(author).join(name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def boolean(value, return_value=False):
|
def boolean(value, return_value=False):
|
||||||
'''
|
'''
|
||||||
Convert a str, bool, int or None object into a boolean.
|
Convert a str, bool, int or None object into a boolean.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
value (str, bool, int, None): The value to be checked
|
value (str, bool, int, None): The value to be checked
|
||||||
return_value (bool): If True, return v instead of True if it can't be converted
|
return_value (bool): If True, return value instead of True if it can't be converted
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
various: A boolean or the value itself
|
various: A boolean or the value itself
|
||||||
|
@ -469,6 +506,72 @@ def var_name(single=True, **kwargs):
|
||||||
return key[0] if single else keys
|
return key[0] if single else keys
|
||||||
|
|
||||||
|
|
||||||
|
class ArgParser(argparse.ArgumentParser):
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._args = None
|
||||||
|
self._arguments = DotDict()
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._arguments[key]
|
||||||
|
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise TypeError(f'Value must be a dict, not {type(value).__name__}')
|
||||||
|
|
||||||
|
self._arguments[key] = Argument(**value)
|
||||||
|
|
||||||
|
|
||||||
|
def add_positional(self, name, **kwargs):
|
||||||
|
self[name] = Argument(name=name, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def add_optional(self, name, short_name=None, **kwargs):
|
||||||
|
self[name] = Argument(name=f'--{name}', short_name=f'-{short_name}', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def add_action(self, name, callback, *args, argument=None, **kwargs):
|
||||||
|
if argument:
|
||||||
|
self[name] = argument
|
||||||
|
|
||||||
|
self[name].set_callback(callback, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def args(self):
|
||||||
|
if not self._args:
|
||||||
|
self._args = self.parse_args()
|
||||||
|
|
||||||
|
return self._args
|
||||||
|
|
||||||
|
|
||||||
|
class Argument(DotDict):
|
||||||
|
valid_keys = ['name', 'short_name', 'nargs', 'default', 'type', 'help', 'metavar']
|
||||||
|
callback = None
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key not in self.valid_keys:
|
||||||
|
raise KeyError(f'Not a valid argument option: {key}')
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == 'name':
|
||||||
|
if not self.metavar:
|
||||||
|
super().__setitem__('metavar', value)
|
||||||
|
|
||||||
|
value = value.replace(' ', '-', '_', '-')
|
||||||
|
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_callback(self, callback, *args, **kwargs):
|
||||||
|
self.callback = lambda *cliargs: callback(*cliargs, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DateString(str):
|
class DateString(str):
|
||||||
tz_utc = timezone.utc
|
tz_utc = timezone.utc
|
||||||
tz_local = datetime.now(tz_utc).astimezone().tzinfo
|
tz_local = datetime.now(tz_utc).astimezone().tzinfo
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import json, os, shutil
|
import json, os, shutil, sys
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
@ -16,6 +16,14 @@ class PathMeta(type):
|
||||||
return cls(os.getcwd()).resolve()
|
return cls(os.getcwd()).resolve()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def script(cls):
|
||||||
|
try: path = sys.modules['__main__'].__file__
|
||||||
|
except: path = sys.argv[0]
|
||||||
|
|
||||||
|
return Path(path).parent
|
||||||
|
|
||||||
|
|
||||||
class Path(str, metaclass=PathMeta):
|
class Path(str, metaclass=PathMeta):
|
||||||
def __init__(self, path=os.getcwd(), exist=True, missing=True, parents=True):
|
def __init__(self, path=os.getcwd(), exist=True, missing=True, parents=True):
|
||||||
#if str(path).startswith('~'):
|
#if str(path).startswith('~'):
|
||||||
|
@ -54,13 +62,28 @@ class Path(str, metaclass=PathMeta):
|
||||||
def __check_dir(self, path=None):
|
def __check_dir(self, path=None):
|
||||||
target = self if not path else Path(path)
|
target = self if not path else Path(path)
|
||||||
|
|
||||||
if not self.config['parents'] and not target.parent.exists:
|
if not self.parents and not target.parent.exists:
|
||||||
raise FileNotFoundError('Parent directories do not exist:', target)
|
raise FileNotFoundError('Parent directories do not exist:', target)
|
||||||
|
|
||||||
if not self.config['exist'] and target.exists:
|
if not self.exist and target.exists:
|
||||||
raise FileExistsError('File or directory already exists:', target)
|
raise FileExistsError('File or directory already exists:', target)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def missing(self):
|
||||||
|
return self.config.missing
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exist(self):
|
||||||
|
return self.config.exist
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parents(self):
|
||||||
|
return self.config.parents
|
||||||
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def isdir(self):
|
def isdir(self):
|
||||||
return os.path.isdir(self)
|
return os.path.isdir(self)
|
||||||
|
@ -137,7 +160,7 @@ class Path(str, metaclass=PathMeta):
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif not self.config.exist:
|
elif not self.exist:
|
||||||
raise FileExistsError(target)
|
raise FileExistsError(target)
|
||||||
|
|
||||||
shutil.copy2(self, target)
|
shutil.copy2(self, target)
|
||||||
|
@ -145,7 +168,7 @@ class Path(str, metaclass=PathMeta):
|
||||||
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
if not self.exists() and not self.config.exist:
|
if not self.exists() and not self.exist:
|
||||||
raise FileNotFoundError(self)
|
raise FileNotFoundError(self)
|
||||||
|
|
||||||
if self.isdir:
|
if self.isdir:
|
||||||
|
@ -170,8 +193,8 @@ class Path(str, metaclass=PathMeta):
|
||||||
return tuple(sorted(self.join(path) for path in paths))
|
return tuple(sorted(self.join(path) for path in paths))
|
||||||
|
|
||||||
|
|
||||||
def join(self, path):
|
def join(self, *paths):
|
||||||
return Path(os.path.join(self, path))
|
return Path(os.path.join(self, *paths))
|
||||||
|
|
||||||
|
|
||||||
def json_load(self):
|
def json_load(self):
|
||||||
|
@ -190,7 +213,7 @@ class Path(str, metaclass=PathMeta):
|
||||||
self.__check_dir(path)
|
self.__check_dir(path)
|
||||||
|
|
||||||
if target.exists():
|
if target.exists():
|
||||||
if not self.config.exist:
|
if not self.exist:
|
||||||
raise FileExistsError(target)
|
raise FileExistsError(target)
|
||||||
|
|
||||||
target.delete()
|
target.delete()
|
||||||
|
@ -207,11 +230,11 @@ class Path(str, metaclass=PathMeta):
|
||||||
|
|
||||||
|
|
||||||
def mkdir(self, mode=0o755):
|
def mkdir(self, mode=0o755):
|
||||||
if self.config.parents:
|
if self.parents:
|
||||||
os.makedirs(self, mode, exist_ok=self.config.exist)
|
os.makedirs(self, mode, exist_ok=self.exist)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
os.makedir(self, mode, exist_ok=self.config.exist)
|
os.makedir(self, mode, exist_ok=self.exist)
|
||||||
|
|
||||||
return self.exists
|
return self.exists
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## Normal SQL client
|
## Normal SQL client
|
||||||
from .database import Database, OperationalError, ProgrammingError
|
from .database import Database, OperationalError, ProgrammingError
|
||||||
from .session import Session
|
from .session import Session
|
||||||
from .column import Column
|
from .table import Column, Table, Tables
|
||||||
from .rows import Row, RowClasses
|
from .rows import Row, RowClasses
|
||||||
|
|
||||||
## Sqlite server
|
## Sqlite server
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
from sqlalchemy import ForeignKey
|
|
||||||
from sqlalchemy import (
|
|
||||||
Column as sqlalchemy_column,
|
|
||||||
types as Types
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SqlTypes = {t.lower(): getattr(Types, t) for t in dir(Types) if not t.startswith('_')}
|
|
||||||
|
|
||||||
|
|
||||||
class Column(sqlalchemy_column):
|
|
||||||
def __init__(self, name, stype=None, fkey=None, **kwargs):
|
|
||||||
if not stype and not kwargs:
|
|
||||||
if name == 'id':
|
|
||||||
stype = 'integer'
|
|
||||||
kwargs['primary_key'] = True
|
|
||||||
kwargs['autoincrement'] = True
|
|
||||||
|
|
||||||
elif name == 'timestamp':
|
|
||||||
stype = 'datetime'
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError('Missing column type and options')
|
|
||||||
|
|
||||||
stype = (stype.lower() if type(stype) == str else stype) or 'string'
|
|
||||||
|
|
||||||
if type(stype) == str:
|
|
||||||
try:
|
|
||||||
stype = SqlTypes[stype.lower()]
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError(f'Invalid SQL data type: {stype}')
|
|
||||||
|
|
||||||
options = [name, stype]
|
|
||||||
|
|
||||||
if fkey:
|
|
||||||
options.append(ForeignKey(fkey))
|
|
||||||
|
|
||||||
super().__init__(*options, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def compile(self):
|
|
||||||
sql = f'{self.name} {self.type}'
|
|
||||||
|
|
||||||
if not self.nullable:
|
|
||||||
sql += ' NOT NULL'
|
|
||||||
|
|
||||||
if self.primary_key:
|
|
||||||
sql += ' PRIMARY KEY'
|
|
||||||
|
|
||||||
if self.unique:
|
|
||||||
sql += ' UNIQUE'
|
|
||||||
|
|
||||||
return sql
|
|
|
@ -22,7 +22,7 @@ modules = dict(
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
def __init__(self, dbtype='sqlite', **kwargs):
|
def __init__(self, dbtype='sqlite', open_now=True, **kwargs):
|
||||||
self._connect_args = [dbtype, kwargs]
|
self._connect_args = [dbtype, kwargs]
|
||||||
self.db = None
|
self.db = None
|
||||||
self.cache = None
|
self.cache = None
|
||||||
|
@ -34,6 +34,7 @@ class Database:
|
||||||
self.session_class = kwargs.get('session_class', Session)
|
self.session_class = kwargs.get('session_class', Session)
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
|
|
||||||
|
if open_now:
|
||||||
self.open()
|
self.open()
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ class Database:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def table(self):
|
def table(self):
|
||||||
return DotDict(self.meta.tables)
|
return self.meta.tables
|
||||||
|
|
||||||
|
|
||||||
def get_tables(self):
|
def get_tables(self):
|
||||||
|
|
125
izzylib/sql/table.py
Normal file
125
izzylib/sql/table.py
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlalchemy import (
|
||||||
|
Column as sqlalchemy_column,
|
||||||
|
types as Types
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..dotdict import DotDict
|
||||||
|
|
||||||
|
|
||||||
|
ptype = type
|
||||||
|
SqlTypes = {t.lower(): getattr(Types, t) for t in dir(Types) if not t.startswith('_')}
|
||||||
|
|
||||||
|
|
||||||
|
class Column(sqlalchemy_column):
|
||||||
|
def __init__(self, name, type=None, fkey=None, **kwargs):
|
||||||
|
if not type and not kwargs:
|
||||||
|
if name == 'id':
|
||||||
|
type = 'integer'
|
||||||
|
kwargs['primary_key'] = True
|
||||||
|
kwargs['autoincrement'] = True
|
||||||
|
|
||||||
|
elif name in ['timestamp', 'created', 'modified', 'accessed']:
|
||||||
|
type = 'datetime'
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError('Missing column type and options')
|
||||||
|
|
||||||
|
type = (type.lower() if ptype(type) == str else type) or 'string'
|
||||||
|
|
||||||
|
if ptype(type) == str:
|
||||||
|
try:
|
||||||
|
type = SqlTypes[type.lower()]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(f'Invalid SQL data type: {type}')
|
||||||
|
|
||||||
|
options = [name, type]
|
||||||
|
|
||||||
|
if fkey:
|
||||||
|
options.append(ForeignKey(fkey))
|
||||||
|
|
||||||
|
super().__init__(*options, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def compile(self):
|
||||||
|
sql = f'{self.name} {self.type}'
|
||||||
|
|
||||||
|
if not self.nullable:
|
||||||
|
sql += ' NOT NULL'
|
||||||
|
|
||||||
|
if self.primary_key:
|
||||||
|
sql += ' PRIMARY KEY'
|
||||||
|
|
||||||
|
if self.unique:
|
||||||
|
sql += ' UNIQUE'
|
||||||
|
|
||||||
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
class Table(list):
|
||||||
|
__table_name__ = None
|
||||||
|
|
||||||
|
def __init__(self, name, *columns, **kwcolumns):
|
||||||
|
super().__init__()
|
||||||
|
self.__table_name__ = name
|
||||||
|
self.columns = []
|
||||||
|
|
||||||
|
self.new('id')
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
|
self.add(column)
|
||||||
|
|
||||||
|
|
||||||
|
def new(self, name, *args, **kwargs):
|
||||||
|
self.add(Column(name, *args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def add(self, column: Column):
|
||||||
|
if column.name == 'id':
|
||||||
|
self.remove('id')
|
||||||
|
|
||||||
|
elif column.name in self.columns:
|
||||||
|
# This needs to be a custom exception. Probably ColumnExistsError
|
||||||
|
raise ValueError(f'Column already exists: {column.name}')
|
||||||
|
|
||||||
|
self.append(column)
|
||||||
|
self.columns.append(column.name)
|
||||||
|
|
||||||
|
|
||||||
|
def remove(self, name):
|
||||||
|
for col in self:
|
||||||
|
if col.name == name:
|
||||||
|
super().remove(name)
|
||||||
|
self.columns.remove(name)
|
||||||
|
|
||||||
|
|
||||||
|
class Tables(DotDict):
|
||||||
|
def __init__(self, *tables, **kwtables):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
self.add(table)
|
||||||
|
self[table.__table_name__] = table
|
||||||
|
|
||||||
|
for name, table in kwtables.items():
|
||||||
|
if isinstance(table, list):
|
||||||
|
self.new(name, *table)
|
||||||
|
|
||||||
|
elif isinstance(table, Table):
|
||||||
|
self.add(table)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise TypeError(f'Invalid table type for {name}: {type(table).__name__}')
|
||||||
|
|
||||||
|
|
||||||
|
def new(self, name, *columns):
|
||||||
|
self[name] = Table(name, *columns)
|
||||||
|
|
||||||
|
|
||||||
|
def add(self, table: Table):
|
||||||
|
self[table.__table_name__] = table
|
||||||
|
|
||||||
|
|
||||||
|
def remove(self, name):
|
||||||
|
del self[name]
|
Loading…
Reference in a new issue