Giter VIP home page Giter VIP logo

trame-cookiecutter's Introduction

Cookiecutter Trame

This is a Cookiecutter template that generates Trame boilerplate.

Quick Start

Install the Cookiecutter package:

pip install cookiecutter

Trame v1

cookiecutter gh:kitware/trame-cookiecutter -c v1

Trame v2

cookiecutter gh:kitware/trame-cookiecutter

What do you get?

This project contains a Cookiecutter template that helps you create new Python 3.6+ package for trame by automatically generating most of the boiler plate content for you.

The cookiecutter will ask you set of questions to refine what you aim to build using trame, but the most important one which will affect the shape of what you will get is the project_type.

The project_type can be only one of the following options:

  • App This will provide the infrastructure for building a trame application that can leverage all the existing set of trame components using only Python. By default a simple vtk.js application is used as reference to provide the trame basic while demonstrating an interactive application with code separation between the Python core and the web frontend. Whithin that Python package, a jupyter helper module will also be available to run and show your application within a jupyter environment. On top of that Python package, you will have access to several bundling option ranging from a standalone executable to a docker image for cloud deployment.
  • App with Components This will provide the same infrastructure as above but with an additional directory that will contain a vue.js project for defining new UI elements that can then be used within your trame application. This path is more advanced than the plain Application one as it will require some Web development knowledge. Also on top of that new web structure, a Python module is getting created to bridge what has been defined on the JavaScript side so it can be used at the Python level.
  • Components This is the same thing as above but without the application part. Basically it creates the Python package so it can be pip installable and used by any trame application at the Python level. But as before stated, this will require some Web development knowledge.

The structure produced by this Cookiecutter template contains the following items:

├── .*                         # (Continuous integration option) Python quality control + Github actions
├── CONTRIBUTING.rst           # Minimal content for project contribution
├── LICENSE                    # Selected license
├── MANIFEST.in                # List external files/directories that needs to be part of the Python package
├── README.rst                 # Minimal README using RST format so it get exposed to PyPI if deployed
├── bundles                    # (App option) Bundling helper for Application
│   ├── desktop                  # How to create a desktop executable
│   │   ├── macOS/*                # On macOS
│   │   └── windows/*              # On Windows
│   └── docker/*                 # How to create docker image for cloud service deployment
├── examples                   # (App option) Usage example of your application
│   └── jupyter/*                # Notebook to use inside Jupyter
├── setup.cfg                  # Configuration file for your Python package
├── setup.py                   # Python package entry point
├── tests/*                    # Testing infrastructure
├── f"{import_name}"           # Root directory for your Python package
│   ├── app                      # (App option) Root directory for your application
│   │   ├── engine.py              # Core Python code for your application
│   │   ├── jupyter.py             # Built-in adapter for usage in Jupyter
│   │   ├── main.py                # Main executable setting-up your engine+ui
│   │   └── ui.py                  # UI definition and bridge to Web frontend
│   ├── module/*                 # (Component option) web files and configuration
│   └── widgets/*                # (Component option) web to Python mapping
├── trame/*                      # (Component only option) adapter to streamline import and usage for trame
└── vue-components/*             # (Component option) Standard Vue.js project for creating a Vue plugin

Configuration options

  • project_name [Trame App]: Human readable name for your application
  • project_type [App]: Project type described in more detail above
  • author: Used for your package definition (setup.cfg)
  • short_description: Used for your package definition (setup.cfg)
  • license [BSD]: Used for your package definition (setup.cfg + LICENSE)
  • include_continuous_integration: Create Github actions with Python quality control validation (.*)
  • package_name: Application name to use for pip install or application execution
  • import_name: Physical name of the root directory of your application/library

Usage example

Caution: If you don't keep the same options, please look at the generated README to properly capture the specificity of your setup.

$ python3 -m venv .venv
$ source ./.venv/bin/activate
$ python -m pip install --upgrade pip
$ pip install cookiecutter

$ cookiecutter gh:kitware/trame-cookiecutter

    project_name [Trame App]: Visualis
    Select project_type:
    1 - App
    2 - App with Components
    3 - Components
    Choose from 1, 2, 3 [1]:
    author [Trame Developer]: Kitware Inc.
    short_description [An example Trame application]: VTK viewer for 3d stuff
    Select license:
    1 - BSD License
    2 - MIT License
    3 - ISC License (ISCL)
    4 - Apache Software License
    5 - GNU General Public License v3 (GPLv3)
    6 - Other
    Choose from 1, 2, 3, 4, 5, 6 [1]: 4
    include_continuous_integration [y]: n
    package_name [visualis]:
    import_name [visualis]:

cd visualis
pip install . # Install your new application

pip install pywebview  # For app usage
pip install jupyterlab # For Jupyter usage

Then you can run it using your browser with the following command line:

visualis

Standard execution with auto browser open

Or show it as a desktop application with the following command line:

visualis --app

Desktop Application mode

Or with Jupyter

jupyter-lab

Jupyter Notebook Example

And if you have docker

cd bundles/docker
./scripts/build_image.sh
./scripts/run_image.sh
#> open http://localhost:8080/

trame-cookiecutter's People

Contributors

andrepearce avatar jourdain avatar psavery avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

trame-cookiecutter's Issues

missing serve directory

Hello!

Ran the following set of commands:

conda create -n test-trame-py312 python=3.12
conda activate test-trame-py312
pip install cookiecutter
cookiecutter gh:kitware/trame-cookiecutter
... App + Components option selected
cd test-trame-py312
pip install .
pip install pywebview
pip install jupyterlab
test-trame-py312

Last command gives me the following error:

Details

$ test-trame-py312
Traceback (most recent call last):
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py", line 561, in init
raise ValueError("Not a directory")
ValueError: Not a directory

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/Users/swelborn/miniconda3/envs/test-trame-py312/bin/test-trame-py312", line 8, in
sys.exit(main())
^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/test_trame_py312/app/main.py", line 5, in main
app.server.start(**kwargs)
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/trame_server/core.py", line 653, in start
task = CoreServer.server_start(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/trame_server/protocol.py", line 50, in server_start
return server.start_webserver(
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/server.py", line 263, in start_webserver
ws_server = create_webserver(server_config, backend=backend)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/server.py", line 173, in create_webserver
return backends.create_webserver(server_config, backend=backend)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/init.py", line 5, in create_webserver
return create_webserver(server_config)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/aiohttp/init.py", line 175, in create_webserver
return WebAppServer(server_config)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/aiohttp/init.py", line 74, in init
self.app.add_routes(routes)
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_app.py", line 373, in add_routes
return self.router.add_routes(routes)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py", line 1212, in add_routes
registered_routes.extend(route_def.register(self))
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_routedef.py", line 98, in register
resource = router.add_static(self.prefix, self.path, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py", line 1136, in add_static
resource = StaticResource(
^^^^^^^^^^^^^^^
File "/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py", line 563, in init
raise ValueError(f"No directory exists at '{directory}'") from error
ValueError: No directory exists at '/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/test_trame_py312/module/serve'

I figured that the example shown in readme would be able to run without creating anything, but is that not true?

This is also happening in Jupyter:

from test_trame_py312.app.core import MyTrameApp

app = MyTrameApp()
await app.ui.ready
app.ui
Details


ValueError Traceback (most recent call last)
File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:561, in StaticResource.init(self, prefix, directory, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
560 if not directory.is_dir():
--> 561 raise ValueError("Not a directory")
562 except (FileNotFoundError, ValueError) as error:

ValueError: Not a directory

The above exception was the direct cause of the following exception:

ValueError Traceback (most recent call last)
Cell In[1], line 4
1 from test_trame_py312.app.core import MyTrameApp
3 app = MyTrameApp()
----> 4 await app.ui.ready
5 app.ui

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/trame_client/ui/core.py:203, in AbstractLayout.ready(self)
199 @Property
200 def ready(self):
201 if not self.server.running:
202 # NoOp if already start[ing/ed]
--> 203 self.server.start(
204 exec_mode="task",
205 port=0,
206 open_browser=False,
207 show_connection_info=False,
208 disable_logging=True,
209 timeout=0,
210 )
211 return self.server.ready

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/trame_server/core.py:653, in Server.start(self, port, thread, open_browser, show_connection_info, disable_logging, backend, exec_mode, timeout, host, **kwargs)
650 CoreServer.configure(options)
652 self._running_stage = 1
--> 653 task = CoreServer.server_start(
654 options,
655 **{ # Do a proper merging/override
656 **kwargs,
657 "disableLogging": disable_logging,
658 "backend": backend,
659 "exec_mode": exec_mode,
660 },
661 )
663 # Manage exit life cycle unless coroutine
664 if exec_mode == "main":

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/trame_server/protocol.py:50, in CoreServer.server_start(options, disableLogging, backend, exec_mode, **kwargs)
41 @staticmethod
42 def server_start(
43 options,
(...)
48 ):
49 # NOTE: **kwargs to wslink's start_webserver are currently unused
---> 50 return server.start_webserver(
51 options=options,
52 protocol=CoreServer,
53 disableLogging=disableLogging,
54 backend=backend,
55 exec_mode=exec_mode,
56 **kwargs,
57 )

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/server.py:263, in start_webserver(options, protocol, disableLogging, backend, exec_mode, **kwargs)
260 server_config["handle_signals"] = not options.nosignalhandlers
262 # Create the webserver and start it
--> 263 ws_server = create_webserver(server_config, backend=backend)
265 # Once we have python 3.7 minimum, we can start the server with asyncio.run()
266 # asyncio.run(ws_server.start())
267
268 # Until then, we can start the server this way
269 loop = asyncio.get_event_loop()

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/server.py:173, in create_webserver(server_config, backend)
172 def create_webserver(server_config, backend="aiohttp"):
--> 173 return backends.create_webserver(server_config, backend=backend)

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/init.py:5, in create_webserver(server_config, backend)
2 if backend == "aiohttp":
3 from .aiohttp import create_webserver
----> 5 return create_webserver(server_config)
7 if backend == "generic":
8 from .generic import create_webserver

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/aiohttp/init.py:175, in create_webserver(server_config)
172 return ReverseWebAppServer(server_config)
174 # Normal web server
--> 175 return WebAppServer(server_config)

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/wslink/backends/aiohttp/init.py:74, in WebAppServer.init(self, server_config)
72 # Resolve / => index.html
73 self.app.router.add_route("GET", "/", _root_handler)
---> 74 self.app.add_routes(routes)
76 self.app["state"] = {}

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_app.py:373, in Application.add_routes(self, routes)
372 def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]:
--> 373 return self.router.add_routes(routes)

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:1212, in UrlDispatcher.add_routes(self, routes)
1210 registered_routes = []
1211 for route_def in routes:
-> 1212 registered_routes.extend(route_def.register(self))
1213 return registered_routes

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_routedef.py:98, in StaticDef.register(self, router)
97 def register(self, router: UrlDispatcher) -> List[AbstractRoute]:
---> 98 resource = router.add_static(self.prefix, self.path, **self.kwargs)
99 routes = resource.get_info().get("routes", {})
100 return list(routes.values())

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:1136, in UrlDispatcher.add_static(self, prefix, path, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
1134 if prefix.endswith("/"):
1135 prefix = prefix[:-1]
-> 1136 resource = StaticResource(
1137 prefix,
1138 path,
1139 name=name,
1140 expect_handler=expect_handler,
1141 chunk_size=chunk_size,
1142 show_index=show_index,
1143 follow_symlinks=follow_symlinks,
1144 append_version=append_version,
1145 )
1146 self.register_resource(resource)
1147 return resource

File ~/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:563, in StaticResource.init(self, prefix, directory, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
561 raise ValueError("Not a directory")
562 except (FileNotFoundError, ValueError) as error:
--> 563 raise ValueError(f"No directory exists at '{directory}'") from error
564 self._directory = directory
565 self._show_index = show_index

ValueError: No directory exists at '/Users/swelborn/miniconda3/envs/test-trame-py312/lib/python3.12/site-packages/test_trame_py312/module/serve'

Hot Reload

Hello,

I see hot reload has recently be added to trame (2.3.0+)

Is hot reload integrated behind the scenes and activated with a passed argument or does the main.py file need to be updated in this cookiecutter?

Thanks

Error in Packaging Desktop Files

After running pyinstaller, I got the desktop file.
When running the desktop file in ubuntu, I got the FileNotFoundError.
image

When running the desktop file in windows, I got the System.NullReferenceException.
image

How to solve the problems. Thanks.

Cannot connect to app running in Docker

I believe I have followed the instructions in the README step by step but I cannot connect to the default trame app running inside a Docker container. I run my experiments inside a virtual machine managed by Vagrant. Note that before trying this in a VM, I also tried directly on my Ubuntu 23.10 workstation (still using a Python virtualenv) and got similar results. I decided to try on a fresh VM in case something was wrong with my system.

Here are Debian and Python versions present in the VM:

vagrant@debian11vm:~/trame-app$ cat /etc/debian_version 
11.8
vagrant@debian11vm:~/trame-app$ python --version
Python 3.9.2

The exact steps I have done are detailed below.

vagrant@debian11vm:~$ python -mvenv trame-cookiecutter-env
vagrant@debian11vm:~$ source trame-cookiecutter-env/bin/activate
(trame-cookiecutter-env) vagrant@debian11vm:~$ ls
index.html  trame-cookiecutter-env
(trame-cookiecutter-env) vagrant@debian11vm:~$ pip install cookiecutter
[...]
(trame-cookiecutter-env) vagrant@debian11vm:~$ cookiecutter gh:kitware/trame-cookiecutter
  [1/8] project_name (Trame App): 
  [2/8] Select project_type
    1 - App
    2 - App with Components
    3 - Components
    Choose from [1/2/3] (1): 
  [3/8] author (Trame Developer): 
  [4/8] short_description (An example Trame application): 
  [5/8] Select license
    1 - Apache Software License
    2 - BSD License
    3 - MIT License
    4 - ISC License (ISCL)
    5 - GNU General Public License v3 (GPLv3)
    6 - Other
    Choose from [1/2/3/4/5/6] (1): 
  [6/8] include_continuous_integration (y): 
  [7/8] package_name (trame-app): 
  [8/8] import_name (trame_app):
(trame-cookiecutter-env) vagrant@debian11vm:~$ cd trame-app/
(trame-cookiecutter-env) vagrant@debian11vm:~/trame-app$ pip install -e .
[...]
trame-cookiecutter-env) vagrant@debian11vm:~/trame-app$ trame-app -i 0.0.0.0 --server
>>> ENGINE(a): Slider updating resolution to 6

App running at:
 - Local:   http://0.0.0.0:8080/
 - Network: http://0.0.0.0:8080/

Note that for multi-users you need to use and configure a launcher.

In another terminal, I test that I can connect to the app:

$ curl http://localhost:8080
302: Found

I also connect to the app using a web browser and I get the cone visualization working correctly. So far so good.
Now let's try to run the app inside Docker.

(trame-cookiecutter-env) vagrant@debian11vm:~/trame-app$ cd bundles/docker/
(trame-cookiecutter-env) vagrant@debian11vm:~/trame-app/bundles/docker$ ./scripts/build_image.sh
[...]
 => exporting to image                                                                                                                                                                                        0.4s
 => => exporting layers                                                                                                                                                                                       0.4s
 => => writing image sha256:e2d52d63bccec443d22d900387cb946d49c9f3ad78079d3c4776375428553475                                                                                                                  0.0s
 => => naming to docker.io/library/trame-app
(trame-cookiecutter-env) vagrant@debian11vm:~/trame-app/bundles/docker$ ./scripts/run_image.sh 
 * Restarting Apache httpd web server apache2                                                                                                                                                                      AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
                                                                                                                                                                                                            [ OK ]
Starting server...
Starting the wslink launcher at
======== Running on http://0.0.0.0:9000 ========
(Press CTRL+C to quit)

Here we can notice a difference between what is mentioned at the end of the README file (open http://localhost:8080/) and what is printed to the terminal.

Trusting what is printed to the terminal as more up-to-date, in another terminal I try first to connect to port 9000:

vagrant@debian11vm:~$ curl http://0.0.0.0:9000
curl: (7) Failed to connect to 0.0.0.0 port 9000: Connection refused

Now I inspect the container to learn more about actual ports being exposed and find that it's more similar to what is mentioned in the README file:

vagrant@debian11vm:~/trame-app$ docker ps --format "{{.Ports}}"
80/tcp, 0.0.0.0:8080->8000/tcp

Trying to access exposed ports:

vagrant@debian11vm:~/trame-app$ curl localhost:80
curl: (7) Failed to connect to localhost port 80: Connection refused
vagrant@debian11vm:~/trame-app$ curl localhost:8080
curl: (56) Recv failure: Connection reset by peer

And finally trying to access the container directly through its IP address withing the interval virtual netowork:

vagrant@debian11vm:~/trame-app$ docker inspect -f "{{.NetworkSettings.IPAddress}}" dreamy_blackwell
172.17.0.2
vagrant@debian11vm:~/trame-app$ curl 172.17.0.2:8000
curl: (7) Failed to connect to 172.17.0.2 port 8000: Connection refused

I started to read the various scripts and docker files present in trame main repository but failing so far to understand what is going on and how to get this to work.

Is anyone having similar issues?

Multiprocessing error in windows bundle

Hello,

I am trying to bundle my trame application into a windows exe. I followed the README steps in bundles/desktop/windows. When I run run.py everything works fine. Then I managed to create the exe with pyinstaller but when I execute it I get the following error:

  File "multiprocessing\process.py", line 314, in _bootstrap
  File "trame_server\utils\desktop.py", line 26, in run
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "PyInstaller\loader\pyimod02_importers.py", line 499, in exec_module
  File "webview\__init__.py", line 22, in <module>
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "PyInstaller\loader\pyimod02_importers.py", line 499, in exec_module
  File "webview\http.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "PyInstaller\loader\pyimod02_importers.py", line 499, in exec_module
  File "bottle.py", line 73, in <module>
AttributeError: 'NoneType' object has no attribute 'write'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "run.py", line 5, in <module>
  File "Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_multiprocessing.py", line 49, in _freeze_support
  File "multiprocessing\spawn.py", line 116, in spawn_main
  File "multiprocessing\spawn.py", line 129, in _main
  File "multiprocessing\process.py", line 329, in _bootstrap
AttributeError: 'NoneType' object has no attribute 'write'

Problem seems to be with the freeze_support(). However when I delete it I have an infinite recursion.
Do you know how to solve this?

Thanks in advance

MacOS App Package

Hello,

How do you create a MacOS version of the app?
I see Windows version is using Pyinstaller and mac is using Py2App.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.