-
Notifications
You must be signed in to change notification settings - Fork 0
Package Structure #20
Comments
Some changes:
|
Passing Config Options via Environment VariablesIn order to keep ECNQueue separate from the API and let the end user configure ECNQueue without directly editing code, we need a way to pass config options from a management script to library code. We can do this using a combination of the python-dotenv library and module level Because a package's For example:
and the # __init__.py
# A made up function
load_file("file.config") if the module is imported from a script called Because of this relative import, we can load environment variables from files next to a management script. |
Suggested Package Structure:
|
LoggerWe need to implement a logger in the top level |
Custom exceptionsThere are several configuration values that need to be correctly configured. However, in order to do this, it makes the most sense to define a custom exception classes that is able to handle the various problems that could occur when trying set the various api configuration values. exceptions.py class exceptionName(Exception):
pass caller.py from exceptions import exceptionName
raise exceptionName("ope! It done broke") The
|
configparser
[webqueue2_api]
[webqueue2_api]
JWT_SECRET_KEY = sshhhhhhh, its a secret!
ENVIRONMENT = dev
[ECNQueue]
QUEUES_TO_IGNORE = ["archives", "drafts", "inbox", "coral"]
QUEUE_DIRECTORY = /home/pier/e/queue/Mail
[Logger]
LOGGER_OUT_FILE = /tmp |
A note on import statements in the webqueue2_api.api package
from .__init__ import app
app.run() To run the api via the
However, it is also possible to run the api in a similar manner by using a wrapper script that functions in a similar way, the big difference being that the wrapper script can contain any code that the user wishes to place in there:
from webqueue2_api.api import app
app.run() It is important to note that whenever an import statement is used, the However, the
from webequeue2_api.api import __main__ Note: since In all three of the examples above: the output will be exactly the same and resemble something like this:
Issues with "duplicate" importsWith many different way to start the api and with many different import statements happening before code is even executed, it is possible to have issues with the api being run twice. For example. the resulting script would run the api twice:
from webqueue2_api.api import app
from webqueue2_api.api import __main__
app.run() output:
In addition to this, it is also pertinent that the |
How it works right nowAt this point in time, it is possible to start the api with gunicorn from the command line. It is possible to stop the api if it is running by running: To effectively run the api with any desired configurations, you must create a config file that resembles this:
Note: make sure to name the file Then, run the following command in the same directory as the config file that was just created: The api is designed (if configurations are omitted from the command line) to fall back and check the current directory for a file with the name Following the above steps will create an api with the desired configurations. Next steps
|
Big changes in the api:The largest change in the api is the addition of a file that contains all of the possible configurations for different areas of the api including the logger, the api, and the ecnqueue. However, this is currently being changed, so that functions in the respective packages will be called as opposed to delaying when the init file for these packages is called, which pull the values directl from the global config file. One of the other large changes is that gunicorn references another script called start.py. Gunicorn is run in a sub process, which calls part of the package without having any context of the fact that it is in a running python script, meaning all changes made before gunicorn is called do not take affect. In its current implementation, the gunicorn sub process calls a function in the start.py module, which returns a flask object that. While this is not optimal, this is the best way I have found to use gunicorn to run the api. |
How to Use the APIIt is possible to interact with the api in one of two ways: via the command line or via a wrapper script Command LineSyntax:
Flags:
Wrapper ScriptSyntax: #wrapper.py
from webqueue2_api import __main__
__main__.startApi(
config_file= "/path/to/config",
log_file= "/path/to/store/logs",
jwt_secret_key= "the secret key",
environment= "dev", # or "prod"
queue_dir= "/path/to/queue/directory",
queues_to_ignore= ["queues", "to", "ignore"],
verbose= True # or False
)
__main__.stopApi() The Config FileThe config file is an alternate way to specify all of the different options that can be used with the api. The config file must follow the following syntax:
The config file can store any and all arguments (except the config_file option) and be used instead of or in conjunction with command line arguments or key word arguments passed in a wrapper script. The order of precedence is as follows:
|
Work for this is now being tracked in the |
The project layout is currently monolithic and difficult to maintain. Its the result of adding features as needed without the foresight of package
To facilitate code splitting and a scalable structure, the layout will be modified to match the following:
This new structure presents several benefits:
|
"webqueue2-api" contains a hyphen which is against package naming conventions. Following suit with GitHub, the package will be renamed to "webqueue2api". |
By default, to import a symbol from a module in a package would require a fully qualified namespace. For example, with the following package structure:
To access the # Fully qualified namespace
import animals.dog.Dog
animals.dog.Dog.bark() # Aliased fully qualified namespace
import animals.dog.Dog as dog
dog.bark() # Alternative aliased fully qualified namespace
from animals.dog import Dog
Dog.bark() This introduced redundancy in imports making code harder to read and it requires an end user to know how the package itself is organized. Following guidance from "What's init for me?", the package will follow the "The Convenience Store" model for module imports. In this example, symbols like values, functions and classes are imported into from .dog import Dog Then the end user could access the from animals import Dog
Dog.bark() This will require new features in the future to be explicitly exported in the |
The previous ECNQueue module containing Item and Queue class definitions as well as utility functions like
These same symbols have also been made available at the top level of
This allows for a workflow like this: import webqueue2api
all_queues = webqueue2api.loadQueues()
ce_queue = webqueue2api.Queue("ce")
aae_1 = webqueue2api.Item("aae", 1) Which is equivalent to: from webqueue2api import loadQueues, Queue, Item
all_queues = loadQueues()
ce_queue = Queue("ce")
aae_1 = Item("aae", 1) Next steps are to implement global configuration using Dataclasses. |
Configuration options for webqueue2api are now being managed using package level dataclass objects. Dataclasses were introduced in Python 3.7. Because templeton runs Python 3.6, we now need to use the dataclasses for Python 3.6 package. Dataclasses are an extension of standard classes that accept instance variable definition and types as well as instance methods then generates an from dataclasses import dataclass
import pathlib
@dataclass
class Configuration:
queue_directory: pathlib.Path = pathlib.Path("/home/pier/e/queue/Mail/")
queues_to_ignore: list = ["archives", "drafts", "inbox", "coral"] The def __init__(self, queue_directory: pathlib.Path = pathlib.Path("/home/pier/e/queue/Mail/"), queues_to_ignore: list = ["archives", "drafts", "inbox", "coral"]):
self.queue_directory = queue_directory
self.queues_to_ignore = queues_to_ignore An instance of this configuration could be made like this: # Default Values
config = Configuration()
# Override Values
config = Configuration(queue_directory="/path/somewhere/else") For each sub-package, a # webqueeu2api/parser/Item.py
from .config import config
def load_queues():
for folder in config.queue_directory:
if folder not in config.queues_to_ignore:
Queue(folder) For parent and sibling modules, the At the top level package, webqueue2api, a master config option will be composed to allow for hierarchical config access liek this: import webqueue2api
webqueue2api..config.parser.queue_directory
webqueue2api..config.api.jwt_secret_key When the package is imported, the default config values are loaded. In order to allow for config file and module execution flags overrides, the instance variable values can be replaced at runtime. Further Reading: https://tech.preferred.jp/en/blog/working-with-configuration-in-python/ |
Package structure was updated with #32 |
Currently, there are only two modules that make up webqueue2_api:
ECNQueue.py
api.py
However
ECNQueue.py
andapi.py
both have several moving parts that should be separated out into various packages and/or modules.ECNQueue.py
has 3 distinct parts:api.py
has 5 distinct parts:ECNQueue.py
)ECNQueue.py
)ECNQueue.py
)As a preliminary structure, it might make sense to make
ECNQueue
andapi
sub-packages ofwebqueue2_api
, with their respective functions outlined above, broken into their own modules:The text was updated successfully, but these errors were encountered: