Giter VIP home page Giter VIP logo

gocelery's Introduction

gocelery

Go Client/Server for Celery Distributed Task Queue

Build Status Coverage Status Go Report Card "Open Issues" "Latest Release" GoDoc License FOSSA Status

Why?

Having been involved in several projects migrating servers from Python to Go, I have realized Go can improve performance of existing python web applications. As Celery distributed tasks are often used in such web applications, this library allows you to both implement celery workers and submit celery tasks in Go.

You can also use this library as pure go distributed task queue.

Go Celery Worker in Action

demo

Supported Brokers/Backends

Now supporting both Redis and AMQP!!

  • Redis (broker/backend)
  • AMQP (broker/backend) - does not allow concurrent use of channels

Celery Configuration

Celery must be configured to use json instead of default pickle encoding. This is because Go currently has no stable support for decoding pickle objects. Pass below configuration parameters to use json.

Starting from version 4.0, Celery uses message protocol version 2 as default value. GoCelery does not yet support message protocol version 2, so you must explicitly set CELERY_TASK_PROTOCOL to 1.

CELERY_TASK_SERIALIZER='json',
CELERY_ACCEPT_CONTENT=['json'],  # Ignore other content
CELERY_RESULT_SERIALIZER='json',
CELERY_ENABLE_UTC=True,
CELERY_TASK_PROTOCOL=1,

Example

GoCelery GoDoc has good examples.
Also take a look at example directory for sample python code.

GoCelery Worker Example

Run Celery Worker implemented in Go

// create redis connection pool
redisPool := &redis.Pool{
  Dial: func() (redis.Conn, error) {
		c, err := redis.DialURL("redis://")
		if err != nil {
			return nil, err
		}
		return c, err
	},
}

// initialize celery client
cli, _ := gocelery.NewCeleryClient(
	gocelery.NewRedisBroker(redisPool),
	&gocelery.RedisCeleryBackend{Pool: redisPool},
	5, // number of workers
)

// task
add := func(a, b int) int {
	return a + b
}

// register task
cli.Register("worker.add", add)

// start workers (non-blocking call)
cli.StartWorker()

// wait for client request
time.Sleep(10 * time.Second)

// stop workers gracefully (blocking call)
cli.StopWorker()

Python Client Example

Submit Task from Python Client

from celery import Celery

app = Celery('tasks',
    broker='redis://localhost:6379',
    backend='redis://localhost:6379'
)

@app.task
def add(x, y):
    return x + y

if __name__ == '__main__':
    ar = add.apply_async((5456, 2878), serializer='json')
    print(ar.get())

Python Worker Example

Run Celery Worker implemented in Python

from celery import Celery

app = Celery('tasks',
    broker='redis://localhost:6379',
    backend='redis://localhost:6379'
)

@app.task
def add(x, y):
    return x + y
celery -A worker worker --loglevel=debug --without-heartbeat --without-mingle

GoCelery Client Example

Submit Task from Go Client

// create redis connection pool
redisPool := &redis.Pool{
  Dial: func() (redis.Conn, error) {
		c, err := redis.DialURL("redis://")
		if err != nil {
			return nil, err
		}
		return c, err
	},
}

// initialize celery client
cli, _ := gocelery.NewCeleryClient(
	gocelery.NewRedisBroker(redisPool),
	&gocelery.RedisCeleryBackend{Pool: redisPool},
	1,
)

// prepare arguments
taskName := "worker.add"
argA := rand.Intn(10)
argB := rand.Intn(10)

// run task
asyncResult, err := cli.Delay(taskName, argA, argB)
if err != nil {
	panic(err)
}

// get results from backend with timeout
res, err := asyncResult.Get(10 * time.Second)
if err != nil {
	panic(err)
}

log.Printf("result: %+v of type %+v", res, reflect.TypeOf(res))

Sample Celery Task Message

Celery Message Protocol Version 1

{
    "expires": null,
    "utc": true,
    "args": [5456, 2878],
    "chord": null,
    "callbacks": null,
    "errbacks": null,
    "taskset": null,
    "id": "c8535050-68f1-4e18-9f32-f52f1aab6d9b",
    "retries": 0,
    "task": "worker.add",
    "timelimit": [null, null],
    "eta": null,
    "kwargs": {}
}

Projects

Please let us know if you use gocelery in your project!

Contributing

You are more than welcome to make any contributions. Please create Pull Request for any changes.

LICENSE

The gocelery is offered under MIT license.

gocelery's People

Contributors

actumn avatar akkumar avatar egor0v avatar fireagainsmile avatar fossabot avatar haritsfahreza avatar henderake avatar kobayashi avatar martincapello avatar rbromley10 avatar sirseim avatar yoonsio avatar

Stargazers

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

Watchers

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

gocelery's Issues

support float32 as arguments

float32 is not supported at the time of writing.
If you run following code, you will run into panic.

// addFloat32 returns sum of two float32 values
func addFloat32(a, b float32) float32 {
	return a + b
}

runtime error if celery task method has no return value

runtime error if celery task method has no return value.

panic: runtime error: index out of range

goroutine 10 [running]:
panic(0x86dbc0, 0xc82000a120)
    /usr/lib/go-1.6/src/runtime/panic.go:481 +0x3e6
github.com/shicky/gocelery.(*CeleryWorker).RunTask(0xc8200165f0, 0xc820168060, 0xc82015bf50, 0x0, 0x0)
    /home/secret/gowork/src/github.com/shicky/gocelery/worker.go:130 +0x87f
github.com/shicky/gocelery.(*CeleryWorker).StartWorker.func1(0xc8200165f0, 0xc820011b90, 0x24)
    /home/secret/gowork/src/github.com/shicky/gocelery/worker.go:55 +0x290
created by github.com/shicky/gocelery.(*CeleryWorker).StartWorker
    /home/secret/gowork/src/github.com/shicky/gocelery/worker.go:69 +0x104

No tasks from celery-php

PHP (https://github.com/gjedeer/celery-php):

const QUEUE_NAME = 'tasks';
$this->queue = new Celery(
		    $host,
            null,
            $password,
            '0',
            self::QUEUE_NAME,
            'celery',
            $port,
            'redis',
            true
        );
...
$asyncResult = $this->queue->PostTask('sample', ['foo'], true);
$queuer->postTask('sample', ['foo']);

In go:

func buildClient(host string, port int, password string, workers int) (*celery.CeleryClient, error) {
	queueBroker := celery.NewRedisCeleryBroker(fmt.Sprintf("redis://:%s@%s:%d/0", password, host, port))
	queueBackend := celery.NewRedisCeleryBackend(fmt.Sprintf("redis://:%s@%s:%d/0", password, host, port))

	client, err := celery.NewCeleryClient(queueBroker, queueBackend, workers)
	if err != nil {
		return nil, err
	}

	return client, nil
}

client, err := buildClient(queueHost, queuePort, queuePass, workers)
client.Register('sample' sampleTask);

However, the go client never sees the tasks submitted.
In Redis in debug logging, I see:

mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:26.140 - Accepted 127.0.0.1:58794
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:26.141 - Client closed connection
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:26.803 - DB 0: 2 keys (0 volatile) in 4 slots HT.
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:26.804 - 0 clients connected (0 slaves), 846196 bytes in use
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:31.255 - Accepted 127.0.0.1:58802
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:31.257 - Client closed connection
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:31.817 - DB 0: 2 keys (0 volatile) in 4 slots HT.
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:31.817 - 0 clients connected (0 slaves), 846196 bytes in use
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:36.382 - Accepted 127.0.0.1:58810
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:36.383 - Client closed connection
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:36.830 - DB 0: 2 keys (0 volatile) in 4 slots HT.
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:36.830 - 0 clients connected (0 slaves), 846196 bytes in use
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:41.490 - Accepted 127.0.0.1:58818
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:41.491 - Client closed connection
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:41.841 - DB 0: 2 keys (0 volatile) in 4 slots HT.
mq_1_9a03f7d641e2 | 1:M 21 Nov 10:45:41.841 - 0 clients connected (0 slaves), 846196 bytes in use

Anything im missing?

In redis;

127.0.0.1:6379> blpop celery 1
1) "celery"
2) "{\"body\":\"eyJpZCI6InBocF81YmY1NzYzNzQ3Yzc3OC4zODY3MDc0NyIsInRhc2siOiJzYW1wbGUiLCJhcmdzIjpbImZvbyJdLCJrd2FyZ3MiOnt9fQ==\",\"headers\":{},\"content-type\":\"application\\/json\",\"content-encoding\":\"binary\",\"properties\":{\"body_encoding\":\"base64\",\"reply_to\":\"php_5bf5763747c778.38670747\",\"delivery_info\":{\"priority\":0,\"routing_key\":\"celery\",\"exchange\":\"celery\"},\"delivery_mode\":2,\"delivery_tag\":\"php_5bf5763747c778.38670747\"}}"

AMQP / AWS SQS

Can I use AMQP to talk to AWS? or is there an AWS backend in the works?
As well and example for AWS would be nice if that is an option of course.

Task should be an interface, not an anonymous function

type Task interface {
    Execute() (result interface{}, err error)
}

This way you can add dependencies without accessing global state.

For example

type AddTask struct {
    A `json:"a"`
    B `json:"b"`
    Cache CachingService
}

Each worker burns one CPU core even if he is only waiting for new messages

The StartWorker() function runs into an loop and on each iteration it wants to get new messages from the broker.

As far as i understood go channels normaly you want explizit the blocking behavior of channel reads and write so that the underling code ony gets executet if there is something on the channel.

In the way you used the select satement brakes this pattern due to the default: case.
Everytime when there is nothing on the channel your code does not stop. Instead it is looping over and over again and burns all the CPU for waiting on tasks.

To be honest im only a GO beginner so I'm not able to fix your code at all. But I would appreciate it when you think about that design pattern.

func (w *CeleryWorker) StartWorker() {

    w.stopChannel = make(chan struct{}, 1)
    w.workWG.Add(w.numWorkers)

    for i := 0; i < w.numWorkers; i++ {
        go func(workerID int) {
            defer w.workWG.Done()
            for {
                select {
                case <-w.stopChannel:
                    return
                default:

                    // process messages
                    taskMessage, err := w.broker.GetTaskMessage()
                    if err != nil || taskMessage == nil {
                        //log.Println("continue")
                        continue
                    }

                    //log.Printf("WORKER %d task message received: %v\n", workerID, taskMessage)

                    // run task
                    resultMsg, err := w.RunTask(taskMessage)
                    if err != nil {
                        log.Println(err)
                        continue
                    }
                    defer releaseResultMessage(resultMsg)

                    // push result to backend
                    err = w.backend.SetResult(taskMessage.ID, resultMsg)
                    if err != nil {
                        log.Println(err)
                        continue
                    }
                }
            }
        }(i)
    }
}

amqp_broker.go

func (b *AMQPCeleryBroker) GetTaskMessage() (*TaskMessage, error) {
    var taskMessage TaskMessage
    select {
    case delivery := <-b.consumingChannel:
        delivery.Ack(false)
        if err := json.Unmarshal(delivery.Body, &taskMessage); err != nil {
            return nil, err
        }
        return &taskMessage, nil
    default:
        return nil, fmt.Errorf("consumingChannel is empty")

    }

}

Kind regards
Reinhard Luediger

Add ability to set Retries

Hi,

Nice library, thanks for this. We would like to adapt this for a soon to be open sourced project. For our use case, ability to set retries is critical(same with ETA but I see an issue already exists for that). Do you plan to support this soon? I can also implement this and make a PR if you don't mind.

Best,
Vimukthi

uuid.NewV4() build error

../../.go/src/github.com/gocelery/gocelery/message.go:26:42: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:27:36: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:28:40: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:39:30: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:40:30: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:47:29: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:119:20: multiple-value uuid.NewV4() in single-value context
../../.go/src/github.com/gocelery/gocelery/message.go:128:23: multiple-value uuid.NewV4() in single-value context

Celery Message Protocol Version 2 Support?

Celery Message Protocol:
http://docs.celeryproject.org/en/latest/internals/protocol.html

[2018-06-21 14:55:50,898: INFO/MainProcess] Received task: injection.data_sync.tasks.write_to_es[2494885c-0361-4be9-9635-c2d578dec026]  ETA:[2018-06-21 14:55:50+08:00] 
[2018-06-21 14:55:50,899: DEBUG/MainProcess] basic.qos: prefetch_count->20
[2018-06-21 14:55:50,900: ERROR/MainProcess] Error in timer: TypeError("'NoneType' object is not iterable",)
Traceback (most recent call last):
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/kombu/asynchronous/hub.py", line 136, in fire_timers
    entry()
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/kombu/asynchronous/timer.py", line 68, in __call__
    return self.fun(*self.args, **self.kwargs)
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 482, in apply_eta_task
    self.on_task_request(task)
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/celery/worker/worker.py", line 223, in _process_task_sem
    return self._quick_acquire(self._process_task, req)
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/kombu/asynchronous/semaphore.py", line 62, in acquire
    callback(*partial_args, **partial_kwargs)
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/celery/worker/worker.py", line 228, in _process_task
    req.execute_using_pool(self.pool)
  File "/Users/gushitong/.virtualenvs/gaia/lib/python2.7/site-packages/celery/worker/request.py", line 520, in execute_using_pool
    time_limit, soft_time_limit = self.time_limits
TypeError: 'NoneType' object is not iterable

Celery 4.2(Consumer) will crash if receive task from gocelery due to Message Protocol Mismatch.

github release

Can we have a github release , (say 0.1.0 or any other semver, as appropriate ) so it can be integrated better with dep

support arrays and maps as arguments

arrays and maps as arguments are not supported at the time of writing.
If you run following code, it will end up with empty arguments.

// addArr concatenates two slices together
func addArr(a, b []interface{}) []interface{} {
	return append(a, b...)
}

who is using gocelery?

Hello folks!
I'd love to hear who is using gocelery for their projects.
With your permission, I'd like to include a list in README.md.
It would be very helpful to understand how this library is being used to for future direction.

Support multiple clients

In current implementation, gocelery workers consume task item from queue and discard if its not intended for them.
When there are multiple CeleryClient, they may consume task items intended for competing CeleryClient and discard all of them.

Needs to investigate celery implementation for the case of multiple workers on same machine.
http://docs.celeryproject.org/en/latest/userguide/workers.html

Possible solutions:

  • Instead of discarding the task item, put it back to the queue. (This may cause starvation issue)
  • Peek at the task item instead of consuming and consume it only when its valid. (this may also cause issues if malformed request is pushed into the queue)

Is gocelery binary safe?

Hi, I would like to send byte array through the queue as a parameter of one function. However I am not sure whether it wouldn't be safer if I for example base64-encoded the byte array and send it as string. Is this library able to accept raw byte array or not?

Add doc.go

Add doc.go for more detailed documentation

Result interface{} returned is always string type

gocelery worker always return result as string

res, _ := asyncResult.Get(5 * time.Second)
fmt.Printf("Result: %v of type: %v\n", res, reflect.TypeOf(res))
  • With celery worker (actual value should be int but this is golang parser limitation)
celery -A worker worker
Result: 8 of type: float64
  • With gocelery worker
go run example/worker/main.go
Result: 8 of type: string

Periodically poll backend database for results

Instead of continuously polling backend database, poll periodically in order to not stress the database.

//gocelery.go
for {
    select {
    case <-timeoutChan:
        // ...
    case <-ticker.C:
        val, err := ar.AsyncGet()
        if err != nil {
            continue
        }
        return val, nil
    }
}

running concurrent tests with multiple CeleryClient and multiple tasks fails

As apparent from below log, TestWorkerClientArgs test is attempting to get registeredTasks map 0xc4200e48d0 instead of 0xc4200e5590 which is from TestWorkerClientKwargs test.

  • both tests have new CeleryClient of its own: 0xc4200e4900, 0xc4200e55c0
  • both tests pass if run individually.
=== RUN   TestWorkerClientKwargs
2016/09/30 20:49:41 kwarg client 0xc4200e4900
2016/09/30 20:49:41 registering task on 0xc4200e48d0 multiply_kwargs
2016/09/30 20:49:41 registered tasks: 0xc4200e48d0 map[multiply_kwargs:0xc4200fabc0]
2016/09/30 20:49:41 getting tasks: 0xc4200e48d0 map[multiply_kwargs:0xc4200fabc0]
2016/09/30 20:49:41 kwarg client 0xc4200a1b00
2016/09/30 20:49:41 registering task on 0xc4200a1ad0 multiply_kwargs
2016/09/30 20:49:41 registered tasks: 0xc4200a1ad0 map[multiply_kwargs:0xc4200fadc0]
2016/09/30 20:49:41 getting tasks: 0xc4200a1ad0 map[multiply_kwargs:0xc4200fadc0]
--- PASS: TestWorkerClientKwargs (0.02s)
=== RUN   TestWorkerClientArgs
2016/09/30 20:49:41 arg client 0xc4200e55c0
2016/09/30 20:49:41 registering task on 0xc4200e5590 multiply
2016/09/30 20:49:41 registered tasks: 0xc4200e5590 map[multiply:0x487510]
2016/09/30 20:49:41 getting tasks: 0xc4200e48d0 map[multiply_kwargs:0xc4200fabc0]
2016/09/30 20:49:41 task multiply_args is not registered
2016/09/30 20:49:41 async result got
--- FAIL: TestWorkerClientArgs (5.01s)
    gocelery_test.go:134: failed to get result: 5s timeout getting result for cdd00c82-e593-45a0-955a-4ac10f2b4b09

Please do NOT rewrite git history

I had quite old version of gocelery from february and today I have updated it with go get -u all. After executing this command I got error message:

From https://github.com/gocelery/gocelery
 + 7e28cee...b2f0dd7 master     -> origin/master  (forced update)
fatal: refusing to merge unrelated histories
package github.com/gocelery/gocelery: exit status 128

Please do not rewrite git history in public repositories, it causes unnecessary troubles!

history

Initialize using predefined Channel and Connection of AMQP

Hi,

Due to performance issues, I need to get the amqp.Connection & amqp.Channel that initialize on NewAMQPCeleryBroker so I can manage when it should be initiated, closed and re-initiated.

I found that it already has NewAMQPConnection and return amqp.Connection & amqp.Channel. But there is no NewAMQPCeleryBroker method that can be initialize using amqp.Connection & amqp.Channel.

Maybe we can add new NewAMQPCeleryBroker function that use amqp.Connection & amqp.Channel as a parameter.

Thanks

Celery worker in go is not receiving task submitted by python client

The following python client works fine with celery worker in python, but does not work with the celery worker written in go. But, when i use the go client to submit tasks, it works.

I would be grateful if anyone could help me out with this issue.

test.py

from celery import Celery

celery = Celery()
celery.config_from_object('celeryconfig')
print celery.send_task('worker.add', (5456, 2878), serializer='json').get()

celeryconfig.py

BROKER_URL = 'amqp://'
CELERY_RESULT_BACKEND = 'amqp://'

CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_ENABLE_UTC = True

worker/main.go

package main

import (
	"fmt"
	"time"

	"github.com/shicky/gocelery"
)

// Celery Task using args
func add(a int, b int) int {
	return a + b
}

// AddTask is Celery Task using kwargs
type AddTask struct {
	a int // x
	b int // y
}

// ParseKwargs parses kwargs
func (a *AddTask) ParseKwargs(kwargs map[string]interface{}) error {
	kwargA, ok := kwargs["x"]
	if !ok {
		return fmt.Errorf("undefined kwarg x")
	}
	kwargAFloat, ok := kwargA.(float64)
	if !ok {
		return fmt.Errorf("malformed kwarg x")
	}
	a.a = int(kwargAFloat)
	kwargB, ok := kwargs["y"]
	if !ok {
		return fmt.Errorf("undefined kwarg y")
	}
	kwargBFloat, ok := kwargB.(float64)
	if !ok {
		return fmt.Errorf("malformed kwarg y")
	}
	a.b = int(kwargBFloat)
	return nil
}

// RunTask executes actual task
func (a *AddTask) RunTask() (interface{}, error) {
	result := a.a + a.b
	return result, nil
}

func main() {
	// create broker and backend
	// celeryBroker := gocelery.NewRedisCeleryBroker("localhost:6379", "")
	// celeryBackend := gocelery.NewRedisCeleryBackend("localhost:6379", "")

	// AMQP example
	celeryBroker := gocelery.NewAMQPCeleryBroker("amqp://")
	celeryBackend := gocelery.NewAMQPCeleryBackend("amqp://")

	// Configure with 2 celery workers
	celeryClient, _ := gocelery.NewCeleryClient(celeryBroker, celeryBackend, 2)

	// worker.add name reflects "add" task method found in "worker.py"
	// this worker uses args
	celeryClient.Register("worker.add", add)
	celeryClient.Register("worker.add_reflect", &AddTask{})

	// Start Worker - blocking method
	go celeryClient.StartWorker()
	// Wait 30 seconds and stop all workers
	time.Sleep(30 * time.Second)
	celeryClient.StopWorker()
}

Add ability to set ETA

Please,

add ability to set message ETA.
Alternately add public method accepting *TaskMessage.

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.