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

Commit

Permalink
feature: file structure documentation (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
bossjones committed Jun 16, 2020
1 parent 665cb13 commit 7243dc4
Show file tree
Hide file tree
Showing 36 changed files with 290 additions and 194 deletions.
84 changes: 84 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,87 @@ Resources
------------------------------------------------------------------------------

https://www.reddit.com/r/PhotoshopRequest/comments/a7fzua/specific_photoshop_this_picture_of_ultron/


Project structure
-------------------------------------------------------------------------------

.. code-block:: console
.
├── bin - small utility executables
├── contrib - user contributions
├── docs - documentation
├── example_notebooks - notebooks to better understand how fastapi works
├── git_hooks - git hookes for contributors
├── hacking - local development scripts for manipulating project
├── images - repo images
├── lint-configs-python - linter files
│ └── python - pylintrc, mypy, etc configs
├── make - make tasks
├── script - utility scripts, for ci etc
├── tasks - pyinvoke python scripts
├── tests - (tests) pytest
│ ├── api - (tests) web related stuff
│ │ ├── api_v1 - (tests)
│ │ ├── crud - (tests)
│ │ └── utils - (tests)
│ ├── commands - (tests)
│ ├── config - (tests)
│ ├── constants - (tests)
│ ├── core - (tests)
│ ├── fixtures - (tests)
│ │ ├── isolated_config_dir - (tests)
│ │ └── simple - (tests)
│ │ └── packs - (tests)
│ │
│ ├── ssl - (tests)
│ ├── u8client - (tests)
│ │ └── models - (tests)
│ └── utils - (tests)
├── ultron8 - application folder
│ ├── api - web related stuff
│ │ ├── api_v1 - v1 web api
│ │ │ └── endpoints - web routes
│ │ ├── core - application configuration, statup events, logging
│ │ │ ├── base -
│ │ │ ├── pagination -
│ │ │ └── trigger -
│ │ ├── crud - all crud stuff
│ │ ├── db - db related stuff
│ │ │ ├── pagination -
│ │ │ └── u_sqlite -
│ │ ├── db_models - sqlalchemy database models
│ │ ├── depends - dependencies for routes definition.
│ │ ├── email-templates - email templates
│ │ │ └── src -
│ │ ├── factories - factories
│ │ ├── middleware - fastapi custom middlewares
│ │ │ └── logging -
│ │ ├── models - pydantic models for this application.
│ │ │ └── system -
│ │ │
│ │ ├── templates - fastapi templates
│ │ ├── utils - fastapi utils
│ │ └── views - fastapi views
│ │
│ ├── commands - click commands for ultronctl
│ ├── config - ultronctl config functions
│ ├── constants - project wide constants
│ ├── core - click commands core
│ ├── docs - (to be deleted) ????????????
│ ├── exceptions - apllication exceptions
│ ├── migrations - alembic migrations
│ ├── serialize - functions and classes responsible for converting structured data to a format that allows sharing of data.
│ │ └── runstate -
│ ├── static - more images
│ ├── u8client - ultron8 client used for programatic api calls ( base on github3.py )
│ │ ├── models - ( base on github3.py )
│ │ ├── structs - ( base on github3.py )
│ │ └── utils - ( base on github3.py )
│ └── utils - global utils
└── vagrant -
4 changes: 2 additions & 2 deletions tasks/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def view_api_docs(ctx, loc="local"):

@task(
incrementable=["verbose"],
pre=[call(view_api_docs, loc="local"), call(view_coverage, loc="local"),],
pre=[call(view_api_docs, loc="local"), call(view_coverage, loc="local")],
)
def browser(ctx, loc="local"):
"""
Expand Down Expand Up @@ -612,7 +612,7 @@ def autoflake(


@task(
pre=[call(clean, loc="local"), call(verify_python_version, loc="local"),],
pre=[call(clean, loc="local"), call(verify_python_version, loc="local")],
incrementable=["verbose"],
aliases=["clean_stubs", "clean_monkeytype"],
)
Expand Down
File renamed without changes.
11 changes: 11 additions & 0 deletions tests/api/models/test_rw_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from datetime import datetime

import pytest

from ultron8.api.models.rwmodel import convert_datetime_to_realworld


@pytest.mark.unittest
def test_api_datetime_is_in_realworld_format() -> None:
dt = datetime.fromisoformat("2019-10-27T02:21:42.844640")
assert convert_datetime_to_realworld(dt) == "2019-10-27T02:21:42.844640Z"
File renamed without changes.
62 changes: 62 additions & 0 deletions tests/api/services/test_jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from datetime import timedelta

import jwt
import pytest

# from app.models.domain.users import UserInDB
from ultron8.api.models.user import UserInDB
from ultron8.api.services.jwt import (
ALGORITHM,
create_access_token_for_user,
create_jwt_token,
get_email_from_token,
)


@pytest.mark.unittest
@pytest.mark.jwtonly
def test_creating_jwt_token() -> None:
token = create_jwt_token(
jwt_content={"content": "payload"},
secret_key="secret",
expires_delta=timedelta(minutes=1),
)
parsed_payload = jwt.decode(token, "secret", algorithms=[ALGORITHM])

assert parsed_payload["content"] == "payload"


@pytest.mark.unittest
@pytest.mark.jwtonly
def test_creating_token_for_user(test_user: UserInDB) -> None:
token = create_access_token_for_user(user=test_user, secret_key="secret")
parsed_payload = jwt.decode(token, "secret", algorithms=[ALGORITHM])

assert parsed_payload["email"] == test_user.email


@pytest.mark.unittest
@pytest.mark.jwtonly
def test_retrieving_token_from_user(test_user: UserInDB) -> None:
token = create_access_token_for_user(user=test_user, secret_key="secret")
email = get_email_from_token(token, "secret")
assert email == test_user.email


@pytest.mark.unittest
@pytest.mark.jwtonly
def test_error_when_wrong_token() -> None:
with pytest.raises(ValueError):
get_email_from_token("asdf", "asdf")


@pytest.mark.unittest
@pytest.mark.jwtonly
def test_error_when_wrong_token_shape() -> None:
token = create_jwt_token(
jwt_content={"content": "payload"},
secret_key="secret",
expires_delta=timedelta(minutes=1),
)
with pytest.raises(ValueError):
get_email_from_token(token, "secret")
22 changes: 20 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
TimeOut,
)

from ultron8.api import settings
from ultron8.api import crud, settings
from ultron8.api.db.u_sqlite.session import SessionLocal
from ultron8.api.factories.users import _MakeRandomNormalUserFactory
from ultron8.api.models.user import UserCreate, UserInDB
from ultron8.web import get_application

from tests.utils.user import authentication_token_from_email
Expand Down Expand Up @@ -398,7 +400,7 @@ def create_mocked_ultron_session(request, mocker):
@pytest.fixture(name="linux_systems_fixture")
def linux_systems_fixture(request: SubRequest, monkeypatch: MonkeyPatch) -> None:
request.cls.systems = {
"Linux": [{"HOME": "/home/test", "XDG_CONFIG_HOME": "~/.config"}, posixpath],
"Linux": [{"HOME": "/home/test", "XDG_CONFIG_HOME": "~/.config"}, posixpath]
}

request.cls.sys_name = "Linux"
Expand Down Expand Up @@ -494,3 +496,19 @@ def mockreturn(path):
return request.cls.home

monkeypatch.setattr(os.path, "expanduser", mockreturn)


# def test_create_user(db: Session) -> None:
# email = random_lower_string()
# password = random_lower_string()
# user_in = UserCreate(email=email, password=password)
# user = crud.user.create(db, user_in=user_in)
# assert user.email == email
# assert hasattr(user, "hashed_password")


@pytest.fixture
def test_user(db: SessionLocal) -> UserInDB:
data: UserCreate = _MakeRandomNormalUserFactory()
user = crud.user.create(db, user_in=data)
return user
5 changes: 5 additions & 0 deletions ultron8/api/core/security.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import bcrypt
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def generate_salt() -> str:
return bcrypt.gensalt().decode()


def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)

Expand Down
16 changes: 16 additions & 0 deletions ultron8/api/models/jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# pylint: disable=E0611
# NOTE: fixes [pylint] No name 'BaseModel' in module 'pydantic'
# SOURCE: https://github.com/nokia-wroclaw/innovativeproject-sudoku/issues/39

from datetime import datetime

from pydantic import BaseModel


class JWTMeta(BaseModel):
exp: datetime
sub: str


class JWTUser(BaseModel):
email: str
28 changes: 28 additions & 0 deletions ultron8/api/models/rwmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# pylint: disable=E0611
# NOTE: fixes [pylint] No name 'BaseModel' in module 'pydantic'
# SOURCE: https://github.com/nokia-wroclaw/innovativeproject-sudoku/issues/39

import datetime

from pydantic import BaseConfig, BaseModel


def convert_datetime_to_realworld(dt: datetime.datetime) -> str:
return dt.replace(tzinfo=datetime.timezone.utc).isoformat().replace("+00:00", "Z")


def convert_field_to_camel_case(string: str) -> str:
return "".join(
word if index == 0 else word.capitalize()
for index, word in enumerate(string.split("_"))
)


class RWModel(BaseModel):
class Config(BaseConfig):
# whether an aliased field may be populated by its name as given by the model attribute, as well as the alias (default: False)
allow_population_by_field_name = True
# a dict used to customise the way types are encoded to JSON; see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeljson
json_encoders = {datetime.datetime: convert_datetime_to_realworld}
# If data source field names do not match your code style (e. g. CamelCase fields), you can automatically generate aliases using alias_generator
alias_generator = convert_field_to_camel_case
11 changes: 11 additions & 0 deletions ultron8/api/models/rwschema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# from app.models.domain.rwmodel import RWModel
from ultron8.api.models.rwmodel import RWModel


class RWSchema(RWModel):
class Config(RWModel.Config):
# NOTE: https://pydantic-docs.helpmanual.io/usage/model_config/
# whether to allow usage of ORM mode
# https://pydantic-docs.helpmanual.io/usage/models/#orm-mode-aka-arbitrary-class-instances
# Pydantic models can be created from arbitrary class instances to support models that map to ORM objects.
orm_mode = True
9 changes: 9 additions & 0 deletions ultron8/api/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,16 @@ class User(UserBaseInDB):

# Additional properties stored in DB
class UserInDB(UserBaseInDB):
salt: Optional[str] = None
hashed_password: str

class Config:
orm_mode = True

# TODO: Enable this so we can securely story password salt+hash. See https://github.com/bossjones/ultron8/issues/63
# def check_password(self, password: str) -> bool:
# return security.verify_password(self.salt + password, self.hashed_password)

# def change_password(self, password: str) -> None:
# self.salt = security.generate_salt()
# self.hashed_password = security.get_password_hash(self.salt + password)
9 changes: 0 additions & 9 deletions ultron8/api/routers/action.py

This file was deleted.

6 changes: 0 additions & 6 deletions ultron8/api/routers/alive.py

This file was deleted.

Empty file removed ultron8/api/routers/chat_ops.py
Empty file.
Empty file removed ultron8/api/routers/config.py
Empty file.
Empty file removed ultron8/api/routers/datastore.py
Empty file.

0 comments on commit 7243dc4

Please sign in to comment.